requestAnimationFrame 是浏览器提供的最合理触发重绘的时机通知机制;它对齐屏幕刷新率、自动暂停省电,需手动递归调用,用 timestamp 计算进度以保证动画稳定。

requestAnimationFrame 不是“用来做动画的函数”,而是浏览器提供的、**最合理触发重绘的时机通知机制**——用错地方或硬套 setTimeout 思路,动画反而会卡顿、掉帧、耗电。
为什么不能直接用 setInterval 或 setTimeout 做动画
定时器不感知屏幕刷新节奏:16ms 间隔只是理论值,实际执行可能因 JS 主线程阻塞而延迟;多次 setTimeout 累积误差会导致动画漂移;iOS Safari 下后台标签页会大幅降频定时器,动画直接停摆。
requestAnimationFrame 由浏览器调度,在下一次重绘前调用回调,天然对齐显示器刷新率(通常是 60Hz),且在页面不可见时自动暂停,省电又精准。
- 必须在每次回调里手动调用下一次
requestAnimationFrame,它不是循环,只触发一次 -
回调函数接收一个参数:
timestamp(DOMHighResTimeStamp),单位毫秒,精度高于Date.now() - 不要在回调里做大量计算或 DOM 批量读写,否则会挤占渲染时间,导致掉帧
基础动画循环怎么写才不出错
一个典型误区是把 requestAnimationFrame 当成 setInterval 的替代品,直接包裹逻辑。正确做法是分离“更新状态”和“应用状态”:
立即学习“Java免费学习笔记(深入)”;
let startTime = null; const duration = 2000; // 动画总时长 2sfunction animate(currentTime) { if (!startTime) startTime = currentTime; const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // 归一化进度 [0, 1]
// 更新样式(推荐用 transform,避免触发布局) element.style.transform =
translateX(${progress * 200}px);if (progress < 1) { requestAnimationFrame(animate); // 只有未完成才继续 } }
requestAnimationFrame(animate);
- 用
currentTime计算进度,而非靠递归次数——这样动画速度才稳定,不受帧率波动影响 - 务必加
if (progress 判断,否则可能多执行一帧,造成微小跳变 - 避免在回调中修改多个元素的 layout 属性(如
width、height、top),优先用transform和opacity
如何取消正在运行的 requestAnimationFrame
requestAnimationFrame 返回一个 ID(类似 setTimeout),但**没有内置的 cancel API 对应物**;必须用 cancelAnimationFrame 显式清除:
let animationId = null;function animate() { // ... 动画逻辑 animationId = requestAnimationFrame(animate); }
// 启动 animationId = requestAnimationFrame(animate);
// 停止时: if (animationId) { cancelAnimationFrame(animationId); animationId = null; }
- 忘记存 ID 或重复调用
cancelAnimationFrame(传入 null/undefined)不会报错,但会导致动画失控 - React/Vue 等框架中,组件卸载时务必清理,否则可能触发已销毁节点的更新,引发报错或内存泄漏
- 如果使用类封装动画,建议把
animationId作为实例属性管理,而不是闭包变量
requestAnimationFrame 和 CSS 动画该选哪个
不是“谁更好”,而是“谁更适合当前场景”:
- CSS 动画(
@keyframes+animation)适合固定路径、无需 JS 交互的过渡效果,GPU 加速好、性能高、代码简洁 -
requestAnimationFrame必须用在需要动态计算(如鼠标跟随、物理模拟、滚动视差)、实时响应用户输入、或逐帧控制状态的场合 - 混合使用常见:用 CSS 做基础入场/出场,用
requestAnimationFrame处理滚动中持续变化的部分(比如视差偏移量) - 注意:强制触发重排(如读取
offsetTop后立刻写style.transform)会破坏 RAF 的优化,导致强制同步布局
真正难的不是调用 requestAnimationFrame,而是判断哪一帧该做什么、哪些操作必须提前批处理、哪些样式变更能合并——这些细节不写进代码里,光靠 API 名字解决不了问题。










