requestAnimationFrame 是浏览器原生 API,用于在下一次重绘前执行动画回调;它由浏览器调度,自动适配刷新率、后台暂停、节电状态,避免 setTimeout 的定时不准、后台耗电、不同步丢帧等问题。

requestAnimationFrame 是什么,为什么比 setTimeout 更适合动画
requestAnimationFrame 是浏览器提供的原生 API,用于在下一次重绘前执行回调函数。它不是“自己控制帧率”,而是把动画逻辑交给浏览器调度——浏览器知道当前刷新率(比如 60Hz)、是否页面在后台、设备节电状态等,自动决定何时触发回调。
用 setTimeout 或 setInterval 模拟 60fps(即每 16.6ms 执行一次)看似可行,但实际会出问题:
- 定时器精度差:JS 事件循环可能被其他任务阻塞,导致回调延迟或堆积
- 页面不可见时仍运行:标签页切走后,
setTimeout还在跑,浪费 CPU 和电量 - 与屏幕刷新不同步:可能在两次重绘之间多次修改样式,造成丢帧或闪烁
requestAnimationFrame 天然规避这些——它只在浏览器准备重绘前调用,且页面隐藏时自动暂停。
最简可用的 requestAnimationFrame 动画写法
核心是「递归调用 + 状态更新」。不要把它当成一次性函数,而是一个持续驱动的循环入口。
立即学习“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]
// 更新元素位置(示例:水平移动) const element = document.getElementById('box'); element.style.transform =
translateX(${progress * 300}px);if (progress < 1) { requestAnimationFrame(animate); // 继续下一帧 } }
// 启动 requestAnimationFrame(animate);
注意点:
- 必须传入回调函数本身(
requestAnimationFrame(animate)),不是requestAnimationFrame(animate()) - 用
currentTime(由浏览器提供的时间戳)计算进度,而非依赖Date.now(),更精准 - 手动控制结束条件(如
progress ),否则无限递归
requestAnimationFrame 在 CSS 动画和 JS 动画中的定位
它不替代 CSS @keyframes,而是补充那些 CSS 做不了的事:比如跟随鼠标实时计算路径、物理模拟(弹性、重力)、滚动视差、Canvas 绘图动画等。
关键区别:
- CSS 动画由渲染引擎直接优化,GPU 加速友好,性能上限高,但逻辑固定
-
requestAnimationFrame提供 JS 层控制权,可读取 DOM 尺寸、响应用户输入、动态调整参数,灵活性强但需小心性能
常见误用:用它反复设置 element.style.left/top 触发 layout → paint → composite 全流程。应优先用 transform 和 opacity,它们只触发 composite,更轻量。
容易被忽略的兼容性与清理问题
requestAnimationFrame 在现代浏览器中已无兼容问题(IE10+ 支持,且有成熟 polyfill),但两个细节常被跳过:
- 动画中途取消:保存返回的 ID,用
cancelAnimationFrame(id)主动停止(比如组件卸载、用户交互中断) - 多个动画共存时未隔离状态:每个动画应有独立的
startTime和结束判断,避免互相干扰
例如,一个轮播组件切换时没 cancel 上一个 requestAnimationFrame,旧动画回调仍可能执行并覆盖新状态——这种竞态很难 debug。











