- Published on
requestAnimationFrame:浏览器动画帧调度
在 Web 开发中,通过 JavaScript 实现流畅、高效的动画是提升用户体验的关键。传统上,开发者常使用 setInterval 等定时器来周期性地更新元素样式,但这种方法与浏览器的渲染机制存在固有的冲突,容易导致动画掉帧和不必要的资源消耗。为了解决这一问题,浏览器提供了一个专门为动画设计的、高性能的 API——window.requestAnimationFrame() (rAF)。它将动画更新的逻辑与浏览器的渲染周期紧密同步,是构建平滑、高效 Web 动画的现代标准。
浏览器渲染帧与 setInterval 的局限性
渲染帧 (The Render Frame)
浏览器并非连续地更新屏幕,而是以一个固定的频率(通常与显示器的刷新率同步,如 60Hz 或 120Hz)进行离散的“帧”绘制。
在 60Hz 的显示器上,浏览器必须在 16.67ms (1000ms / 60) 的时间窗口内,完成从 JavaScript 执行到屏幕像素呈现的完整渲染管线(Style → Layout → Paint → Composite)。如果这一系列工作的耗时超出了这个“帧预算”,浏览器就会放弃当前帧的绘制,导致“掉帧 (frame drop)”,在视觉上表现为动画的卡顿或跳跃。
setInterval 的时序问题
setInterval(callback, delay) 的工作机制,仅仅是每隔 delay 毫秒,尝试将 callback 放入宏任务队列 (macrotask queue)。它的执行时机与屏幕刷新周期完全无关,这会导致两个核心问题:
setInterval 动画的缺陷- 时序不精确:
setInterval无法保证回调在delay毫秒后被精确执行,它只能保证在delay毫秒后被加入任务队列,实际执行时间取决于主线程的繁忙程度。 - 视觉延迟与掉帧: 如果回调的执行时机恰好在浏览器刚刚完成一帧绘制之后,那么本次计算出的新样式将错过当前渲染周期,只能等待下一帧才被绘制,造成视觉上的延迟。反之,如果执行时机太晚,错过了当前帧的绘制窗口,同样会造成“掉帧”。
- 资源浪费: 即使用户切换到其他标签页,导致当前页面不可见,
setInterval的定时器依然会在后台持续运行,不必要地消耗 CPU 和电池资源。
requestAnimationFrame (rAF) 的核心机制
requestAnimationFrame (rAF) 是一个由浏览器提供的、专门用于调度动画渲染的 API。
精确的执行时机
rAF 的核心优势在于,它向浏览器注册的回调函数,不是由一个独立的定时器驱动,而是被放入一个由浏览器严格管理的动画帧回调队列中。
浏览器会在每一帧的渲染管线启动之前,清空并执行这个动画帧回调队列中的所有函数。具体来说,它发生在执行 JavaScript(已在队列中的任务)之后,进行样式计算 (Style) 和布局 (Layout) 之前。
这种机制完美地保证了:
- 在 rAF 回调中对 DOM 进行的所有样式变更,都能够被应用到即将绘制的当前帧中,不会错过时机。
- 浏览器可以智能地将同一帧内的多次 rAF 回调进行优化和合并,避免不必要的计算。
API 签名与递归循环
requestAnimationFrame 接收一个回调函数作为参数,并返回一个请求 ID。该回调函数会接收一个由浏览器传入的、高精度的时间戳 DOMHighResTimeStamp(与 performance.now() 的值类似),表示回调开始执行的精确时间。
requestAnimationFrame 不是一个循环,它只负责安排下一次的单帧回调。要创建一个连续的动画循环,必须在回调函数内部,再次调用 requestAnimationFrame 来注册下一帧的回调。这种递归调用的模式是 rAF 动画的标准实践。
requestAnimationFrame 实现平滑动画const element = document.getElementById('animate');
let start = null; // 用于存储动画开始的时间
function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
// 计算元素的新位置 (例如,每秒移动 100px)
element.style.transform = translateX(${Math.min(progress / 10, 200)}px);
if (progress < 2000) { // 动画持续 2 秒
// 递归调用,注册下一帧的 step 函数
window.requestAnimationFrame(step);
}
}
// 启动动画循环
window.requestAnimationFrame(step);
智能的资源管理
rAF 的另一个关键优势是其对资源的智能管理。当网页在浏览器中处于不可见状态(例如,标签页被切换到后台、浏览器窗口最小化)时,浏览器会自动暂停 requestAnimationFrame 回调的执行,直到页面再次变为可见。这极大地节省了 CPU 和电池资源,是 setInterval 无法比拟的。