requestAnimationFrame
(通常缩写为 rAF
) 是一个浏览器提供的核心 API,用于高效地执行动画。它允许开发者将动画更新函数(重绘操作)安排在浏览器下一次重绘之前执行。
核心目的
实现流畅、高效、与屏幕刷新率同步的动画。相比于使用 setTimeout
或 setInterval
,rAF
具有以下显著优势:
- 同步刷新率:
rAF
的回调函数会在浏览器下一次重绘周期执行。在标准的 60Hz 显示器上,这大约每 16.7ms (1000ms / 60) 执行一次,能充分利用显示器的刷新能力,达到 60 FPS 的流畅效果。 - 自动节流:当标签页不可见(如切换到其他标签页或最小化)时,浏览器会自动暂停
rAF
的执行,节省 CPU、GPU 和电池资源。而setTimeout
在后台仍会继续执行。 - 减少卡顿:浏览器会优化
rAF
的调用时机,尽量确保在每一帧的开始执行,避免与浏览器自身的渲染工作冲突,从而减少掉帧和卡顿。 - 更精确的时间戳:
rAF
的回调函数会接收一个高精度的时间戳(DOMHighResTimeStamp
),表示动画开始的时间,便于计算精确的动画进度。
基本语法
const handle = window.requestAnimationFrame(callback);
callback
:一个函数,它会在浏览器下一次重绘之前被调用。- 返回值:一个
long
类型的整数句柄 (handle
),可用于取消动画请求。
回调函数参数
callback
函数会接收一个参数:
time
(或timestamp
): 一个DOMHighResTimeStamp
类型的数值,表示从页面加载(或performance.timing.navigationStart
)到rAF
回调触发时的毫秒数。这个时间非常精确,是计算动画进度的理想依据。
如何使用 requestAnimationFrame
创建动画
rAF
本身只执行一次。要创建持续的动画,需要在 callback
函数内部递归地再次调用 requestAnimationFrame
。
// 1. 定义动画起始状态
let startTime = null;
const duration = 2000; // 动画持续时间,2秒
const startValue = 0;
const endValue = 100;
function animate(currentTime) {
// 2. 第一次调用时记录开始时间
if (startTime === null) {
startTime = currentTime;
}
// 3. 计算已过去的时间
const elapsed = currentTime - startTime;
// 4. 计算动画进度 (0 到 1)
const progress = Math.min(elapsed / duration, 1); // Math.min 确保不超过 1
// 5. 根据进度计算当前值 (这里用线性插值)
const currentValue = startValue + (endValue - startValue) * progress;
// 6. 更新元素 (执行视觉变化)
document.getElementById('myElement').style.transform = `translateX(${currentValue}px)`;
// 7. 如果动画未完成,继续请求下一帧
if (progress < 1) {
requestAnimationFrame(animate);
} else {
console.log('Animation completed!');
}
}
// 8. 启动动画
requestAnimationFrame(animate);
取消动画
使用 cancelAnimationFrame(handle)
可以取消一个已请求但尚未执行的 rAF
回调。
let animationHandle = null;
function startAnimation() {
function frame(currentTime) {
// ... 动画逻辑
animationHandle = requestAnimationFrame(frame);
}
animationHandle = requestAnimationFrame(frame);
}
function stopAnimation() {
if (animationHandle !== null) {
cancelAnimationFrame(animationHandle);
animationHandle = null;
}
}
与 setTimeout
/setInterval
的对比
特性 | requestAnimationFrame | setTimeout / setInterval |
---|---|---|
执行时机 | 在浏览器下一次重绘之前执行。 | 在指定的延迟后执行,不考虑重绘周期。 |
刷新率同步 | 是,自动与屏幕刷新率同步 (如 60 FPS)。 | 否,时间间隔固定,可能导致跳帧或卡顿。 |
后台行为 | 自动暂停,节省资源。 | 继续执行,消耗资源。 |
性能 | 更高,减少不必要的重绘,更流畅。 | 较低,可能在非重绘时机执行,浪费。 |
时间精度 | 提供高精度时间戳 (DOMHighResTimeStamp )。 | 使用 Date.now() ,精度较低。 |
适用场景 | 首选用于视觉动画和需要流畅更新的场景。 | 适用于非视觉的定时任务、轮询、或对时间精度要求不高的简单动画。 |
最佳实践
- 用于动画:
rAF
是创建平滑 CSS/JS 动画、游戏循环、滚动效果等的首选方法。 - 避免复杂计算:确保
rAF
回调中的代码执行时间远小于 16ms(60 FPS 下),否则会导致掉帧。将耗时计算移到 Web Workers 或使用scheduler.postTask
。 - 组合使用:对于非动画的定时任务,优先使用
setTimeout
/setInterval
。rAF
专为视觉更新而生。 - 利用时间戳:始终使用回调参数
time
来计算动画进度,而不是依赖固定的间隔。 - 及时清理:在动画结束或组件销毁时,使用
cancelAnimationFrame
清理未完成的请求,防止内存泄漏。
总结
requestAnimationFrame
是现代 Web 开发中实现高性能动画的基石。它通过将动画更新与浏览器的渲染循环紧密耦合,确保了动画的流畅性、高效性和节能性。理解并正确使用 rAF
是创建卓越用户体验的关键。