requestanimationframe(raf)通过将回调交由浏览器在下一帧绘制前执行,自动合并样式计算与布局、跳过不可见页帧、适配设备刷新率并暂停时自动停止,从而避免卡顿。

用 requestAnimationFrame(简称 rAF)做动画循环,核心是让浏览器按自身刷新节奏驱动帧更新,而不是靠 setTimeout 或 setInterval “硬塞”时间点。这样能避免掉帧、跳帧和强制同步重排,显著减少卡顿。
为什么 rAF 能避免卡顿
rAF 不是“每 16ms 执行一次”,而是把回调交给浏览器,在下一帧绘制前执行。浏览器会自动: - 合并样式计算与布局(避免强制同步回流) - 跳过不可见标签页的帧(节省资源) - 根据设备刷新率动态调整(如 120Hz 屏幕实际约 8.3ms 一帧) - 暂停动画时自动停止调用(无需手动清理)
正确写法:递归调用 + 时间戳控制
别用 setInterval(rAF, 16) 这类错误组合。标准写法是:
function animate(timestamp) {
// timestamp 是高精度时间戳(DOMHighResTimeStamp),单位毫秒
// 可用于计算 deltaT,实现匀速动画,不依赖帧率
update(deltaT);
render();
requestAnimationFrame(animate); // 下一帧继续
}
requestAnimationFrame(animate);
关键点:
- 使用传入的 timestamp 计算本次帧与上一帧的时间差(deltaT = timestamp - lastTime),所有运动逻辑基于此,而非假设“每帧 16ms”
- 不在 update 中触发强制重排(如读取 offsetTop、getBoundingClientRect()),否则会打断渲染流水线
- 若动画需暂停/停止,用 cancelAnimationFrame(id),记得保存返回的 id
配合 CSS 动画或 transform 提升性能
rAF 控制的是 JS 逻辑节奏,但实际渲染是否卡顿,还取决于你改了什么属性:
- ✅ 优先用
transform和opacity—— 它们可由合成器线程独立处理,不触发布局(Layout)和绘制(Paint) - ❌ 避免频繁修改
top/left/width/height或触发:hover等样式重算 - ? 如果只是位移+缩放,考虑直接用 CSS @keyframes +
animation-play-state控制,比 JS rAF 更轻量
简单防卡顿检查清单
- 动画函数里没读取 offsetLeft、scrollTop 等会触发回流的属性
- 每一帧只做必要计算,复杂逻辑(如物理模拟)可降频执行(例如每 3 帧算一次)
- 使用
will-change: transform提前提示浏览器该元素将变化(慎用,仅对长期动画元素) - 在 DevTools 的 Performance 面板录制动画,观察主线程是否长时间阻塞(长任务 > 16ms 就可能丢帧)











