requestAnimationFrame 写动画必须基于真实时间戳计算 deltaTime,用 speed×deltaTime 更新位置,首次调用记录时间,组件卸载或动画完成时须手动 cancelAnimationFrame 防泄漏。

直接用 requestAnimationFrame 写动画,不是“加个定时器换帧”那么简单——它不自动控制帧率,也不处理时间差,更不会帮你补间。你得自己管好时间、状态和终止逻辑,否则动画会漂移、卡顿或停不下来。
为什么不能直接替换 setTimeout?
requestAnimationFrame 只是告诉浏览器“下一帧我打算画点东西”,它不承诺执行时机,也不提供已过去多少毫秒。常见错误是写成这样:
function animate() {
update(); // 没有传入时间戳
render();
requestAnimationFrame(animate);
}
animate();
这会导致动画速度依赖于帧率波动(比如从 60fps 掉到 30fps,物体就突然变慢)。真正可靠的动画必须基于**真实经过的时间**做位移计算。
- 务必使用回调参数中的时间戳(
DOMHighResTimeStamp),它是自页面加载以来的高精度毫秒数 - 不要用
Date.now()或performance.now()在回调里重取——虽然值接近,但可能因调度延迟引入误差 - 首次调用时没有“上一帧时间”,需在第一次回调中记录并跳过更新
如何写出不漂移的位移动画?
核心是:用两次时间戳算出 deltaTime(单位毫秒),再乘以速度(如 px/ms)得到本次应移动距离。例如让一个 div 以 100px/s 匀速向右移动:
立即学习“Java免费学习笔记(深入)”;
let lastTime = 0;
function animate(currentTime) {
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
const speed = 100 / 1000; // px/ms
element.style.transform = translateX(${position + deltaTime * speed}px);
position += deltaTime * speed;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
- 速度单位统一转成
px/ms,避免每帧都除 1000 - 不要用
+= 1这类固定步进——那等于假设每帧正好 16.67ms,实际不可能 - 如果动画需精确持续时长(如 500ms),用
currentTime - startTime算进度比用帧数更可靠
什么时候该停止 requestAnimationFrame?
它不会自动停,离开页面、组件卸载、动画完成时都必须手动取消,否则持续占用 CPU 并可能引发内存泄漏。关键点:
- 保存返回的
id(是个数字),用cancelAnimationFrame(id)终止 - 页面不可见时(
document.hidden === true),浏览器会自动降频甚至暂停 rAF,但别依赖这个——仍要监听visibilitychange主动暂停 - React/Vue 等框架中,在组件
useEffect的 cleanup 或beforeUnmount钩子中 cancel - 不要在动画函数内部直接调用
cancelAnimationFrame后还继续递归——容易漏掉最后一帧渲染
最易被忽略的是时间精度与清理时机的配合:哪怕只多执行一帧,若此时 DOM 元素已被移除,element.style 就会报错;而清理太早,又可能丢掉结束态。动手前先想清楚“谁负责启停”和“时间从哪来”。











