requestAnimationFrame(cb) xếp lịch cho callback chạy ngay trước lần vẽ tiếp theo của browser.
let id
function animate(timestamp) {
// timestamp: mili-giây kể từ khi trang tải, dùng để tính delta thay vì đoán
el.style.transform = `translateX(${(timestamp / 10) % 300}px)`
id = requestAnimationFrame(animate)
}
id = requestAnimationFrame(animate)
cancelAnimationFrame(id) // dừng lạiBa lý do hơn setTimeout(fn, 16):
1. Đồng bộ với nhịp vẽ thật của màn hình — đúng cả trên màn 120Hz, không bị lệch khung gây giật.
2. Tự dừng khi tab ẩn — không đốt CPU và pin cho animation không ai nhìn.
3. Chạy đúng thời điểm trong pipeline, nên thay đổi style được gom vào cùng một lần vẽ.
Một công dụng khác ít người biết — gom việc đọc/ghi layout để tránh layout thrashing:
requestAnimationFrame(() => {
const h = el.offsetHeight // đọc
requestAnimationFrame(() => {
el.style.height = h + 'px' // ghi ở khung sau
})
})Lưu ý: rAF không phải bộ đếm thời gian. Tần suất gọi phụ thuộc màn hình và tải máy — luôn tính chuyển động theo timestamp chứ đừng giả định mỗi lần gọi là 16.6ms, nếu không animation sẽ chạy nhanh gấp đôi trên màn 120Hz.
Ghi chú: animation chỉ để trang trí nên để CSS (transition, @keyframes) lo — chúng chạy trên compositor, không cần main thread. rAF dành cho animation phụ thuộc dữ liệu hoặc thao tác người dùng.