requestAnimationFrame 是浏览器原生的重绘调度器,按屏幕刷新率执行动画回调,比 setTimeout 更精准、节能、顺滑;它自动暂停非激活页面动画,避免丢帧和无效计算,且提供高精度时间戳用于帧率自适应动画。

requestAnimationFrame 是什么,为什么不用 setTimeout 做动画?
requestAnimationFrame 是浏览器提供的原生 API,用于在下一次重绘前执行动画回调。它不是定时器,而是“重绘调度器”——浏览器知道你打算做动画,会自动把回调塞进渲染流水线里,和屏幕刷新率(通常是 60Hz)对齐。
用 setTimeout 或 setInterval 模拟动画时,你无法控制回调实际执行时机:可能两次回调挤在同一帧,也可能跨帧丢帧,还可能在页面非激活状态(比如切到其他 tab)下继续执行,白耗 CPU。
而 requestAnimationFrame 自动暂停非可见页面的回调,且保证每次只调一次、紧贴刷新节奏,天然防抖、节能、顺滑。
怎么写一个基础的 requestAnimationFrame 动画循环?
核心是“递归调用 + 状态更新 + 条件退出”,不是一次性注册。常见错误是漏掉下一次调用,或没清空上一次 ID 导致失控。
立即学习“Java免费学习笔记(深入)”;
- 必须在回调函数末尾再次调用
requestAnimationFrame,否则只执行一帧 - 用返回值(一个数字 ID)配合
cancelAnimationFrame手动终止,比如用户点击暂停、元素移出视口 - 动画逻辑里别直接操作 DOM 样式,优先用
transform和opacity,避免触发重排(layout)
let animationId = null;
const element = document.getElementById('box');
let x = 0;
function animate() {
x += 2;
if (x > 400) return; // 退出条件
element.style.transform = translateX(${x}px);
animationId = requestAnimationFrame(animate); // 关键:递归调度
}
animationId = requestAnimationFrame(animate);
// 暂停时调用:
// cancelAnimationFrame(animationId);
// animationId = null;
requestAnimationFrame 的时间戳参数有什么用?
回调函数默认接收一个高精度时间戳(单位毫秒,从页面加载开始),类型是 DOMHighResTimeStamp。它比 Date.now() 更准,且不受系统时间调整影响,适合做匀速、缓动、帧率自适应动画。
例如实现“每秒移动 100px”的匀速位移,不能靠帧数(因为帧率可能波动),而应基于时间差计算:
- 记录上一帧的时间戳
- 当前帧减去上一帧,得到真实经过的毫秒数
- 用这个 delta 计算本次该走多远:
distance = speed * (delta / 1000)
let lastTime = 0; const speed = 100; // px/sfunction animate(time) { if (!lastTime) lastTime = time; const delta = time - lastTime; lastTime = time;
const moveX = (speed * delta) / 1000; element.style.transform =
translateX(${x + moveX}px);requestAnimationFrame(animate); }
requestAnimationFrame(animate);
兼容性与常见陷阱
requestAnimationFrame 在现代浏览器中已全覆盖(包括 iOS Safari 6.1+、Android Browser 4.4+),但旧安卓 WebView 或 IE9- 需要 polyfill。不过现在基本不用考虑了。
真正容易踩的坑是:在动画中频繁读取 offsetTop、getBoundingClientRect() 这类会强制同步触发重排的属性。哪怕只读一次,也可能打断浏览器的渲染优化,导致卡顿。
还有就是忘记清理。比如组件卸载、路由跳转后,如果 requestAnimationFrame 还在跑,就会持续占用资源,甚至引发内存泄漏——尤其在 React/Vue 等框架里,务必在销毁钩子中调用 cancelAnimationFrame。
时间戳不是万能的。如果动画逻辑本身很重(比如大量计算或 DOM 操作),哪怕用了 requestAnimationFrame,依然会掉帧。这时候得拆逻辑、用 Web Worker,或者改用 CSS 动画。











