requestAnimationFrame:实现流畅动画的底层利器
在前端动画开发中,很多人都听过甚至使用过 requestAnimationFrame,但真正理解它为何优于 setTimeout 或 setInterval 的并不多。本文将结合浏览器的渲染原理,从基础讲起,逐步分析它的工作机制与使用优势,帮助你掌握动画优化的正确姿势。
一、为什么需要 requestAnimationFrame?
传统上,我们使用 setInterval(fn, 16) 来尝试实现每秒约 60 次的动画更新(60Hz),即每帧 16ms。但是这种方式存在诸多问题:
-
执行时机不同步:定时器无法保证在浏览器渲染前执行,可能导致:
- 回调执行时浏览器尚未准备好渲染
- 多次回调在同一帧内执行
- 回调执行被延迟导致跳过帧
-
资源消耗问题:
- 页面在后台时仍持续执行
- 频繁触发导致不必要的计算
- 无法享受浏览器的内部优化
为此,requestAnimationFrame 诞生,它是浏览器专门为高性能动画设计的帧调度 API。
二、浏览器的渲染周期与 requestAnimationFrame 的关系
浏览器的渲染流程(简化版)
- JavaScript 执行
- 样式计算(Style)
- 布局(Layout)
- 绘制(Paint)
- 合成(Composite)
现代浏览器的默认刷新频率是 60Hz,即每 16.66ms 会尝试渲染一次。
而 requestAnimationFrame 的本质就是:在浏览器准备开始下一次重绘前调用你的回调函数,这样你可以“插队”到绘制前完成逻辑计算,从而让动画更加平滑。
关键点总结:
- 回调在渲染帧执行前触发
- 可以避免“提前或滞后”的更新
- 浏览器有权决定帧率(例如电池优化)
三、基本语法与用法
function draw() {
// 执行绘制逻辑
console.log('一帧开始前执行');
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
取消帧调度:
const id = requestAnimationFrame(draw);
cancelAnimationFrame(id);
四、requestAnimationFrame 的优势详解
1. 与屏幕刷新同步,避免掉帧
setInterval 是“盲目触发”的定时器,不关心浏览器的帧率。即使你设置了每 16ms 一次,也不能保证和屏幕刷新同步,容易发生丢帧或抖动。
// setInterval 示例(不推荐)
setInterval(() => {
updatePosition();
}, 16);
2. 节能环保,自动暂停后台动画
在浏览器切到后台标签时:
setInterval仍在执行,浪费资源requestAnimationFrame会自动暂停
这对移动端尤为关键,能显著节省电池。
3. 更平滑的帧率控制
浏览器决定每秒调用多少次回调,可以根据性能动态调整帧率,从而避免“卡顿”。
五、requestAnimationFrame 与 setInterval 动画对比
setInterval 的方式
let pos = 0;
setInterval(() => {
pos += 2;
box.style.transform = `translateX(${pos}px)`;
}, 16);
requestAnimationFrame 的方式
let pos = 0;
function step() {
pos += 2;
box.style.transform = `translateX(${pos}px)`;
requestAnimationFrame(step);
}
requestAnimationFrame(step);
对比表
| 特性 | setInterval / setTimeout | requestAnimationFrame |
|---|---|---|
| 精度控制 | 易丢帧、抖动 | 精准控制,按需更新 |
| 资源使用 | 一直运行 | 后台标签自动暂停 |
| 与屏幕同步 | 否 | 是 |
| 使用体验 | 需要配合复杂节流处理 | API 自带节流优化 |
| 应用场景 | 一般定时任务 | 动画、滚动监听、图表重绘等 |
六、最佳实践:封装动画控制器
class Animator {
constructor(callback) {
this.callback = callback;
this.running = false;
this.frameId = null;
}
start() {
if (this.running) return;
this.running = true;
const loop = () => {
this.callback();
if (this.running) {
this.frameId = requestAnimationFrame(loop);
}
};
this.frameId = requestAnimationFrame(loop);
}
stop() {
this.running = false;
cancelAnimationFrame(this.frameId);
}
}
七、兼容性与 Polyfill
大多数现代浏览器都已支持 requestAnimationFrame,如需兼容 IE9,可使用以下 Polyfill:
window.requestAnimationFrame = window.requestAnimationFrame ||
function(callback) {
return setTimeout(callback, 1000 / 60);
};
window.cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
八、总结:何时优先考虑使用 requestAnimationFrame?
- 页面滚动吸顶动画
- canvas 或 SVG 的动画渲染
- 图片或图表的平滑更新
- 滚动监听节流(代替 throttle)
- 游戏帧控制循环
九、更深入的思考
你可以将 requestAnimationFrame 理解为浏览器开放的“渲染钩子”,只有在你准备好更新 UI 的时候调用它,才能最大程度发挥它的性能优势。
此外,requestAnimationFrame也是会被阻塞引起卡顿的,这点需要注意。
🧠 小贴士:若你需要在不活跃状态下依然进行更新(如计时器),
setTimeout是更合适的方式;但只要涉及 DOM 渲染和视觉表现,请优先使用 requestAnimationFrame。
参考资料
更多推荐



所有评论(0)