requestAnimationFrame 比 setInterval 更适合动画,因其自动对齐屏幕刷新率(如60fps)、后台暂停省资源、回调精准触发于绘制前、支持精确启停;需递归调用、用时间戳计算deltaTime避免帧率依赖,并避免DOM读写和CSS动画冲突。
requestAnimationFrame 为什么比 setInterval 更适合做动画
因为 requestAnimationFrame 会自动对齐浏览器的刷新节奏(通常是 60fps),而 setInterval 是纯 JS 计时器,不受屏幕刷新率约束,容易出现掉帧、卡顿或“撕裂”感。更关键的是:页面切到后台时,requestAnimationFrame 会被浏览器暂停,setInterval 却照常执行——这不仅浪费 CPU,还可能让动画状态错乱。
- 用
setInterval(fn, 16)模拟 60fps?实际执行间隔可能漂移,尤其在 JS 主线程繁忙时 -
requestAnimationFrame的回调总在下一帧绘制前触发,天然适配渲染管线 - 它返回一个 ID,可用
cancelAnimationFrame(id)精确控制停帧,比clearInterval更可靠
最简 requestAnimationFrame 动画循环写法
别套用 setInterval 的思维去“轮询”,requestAnimationFrame 是递归驱动的。核心就三步:定义动画函数 → 在函数末尾调用自己 → 启动一次。
let animationId = null;
function animate() {
// 更新状态:比如 this.x += this.speed
// 渲染:比如 ctx.fillRect(this.x, this.y, 10, 10)
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate); // 启动
- 务必把
requestAnimationFrame(animate)放在函数末尾,不是开头 - 启动只需调用一次
requestAnimationFrame(animate),不是animate() - 想暂停?直接
cancelAnimationFrame(animationId);恢复?再调一次requestAnimationFrame(animate)
如何处理动画时间差(delta time)避免速度受帧率影响
直接用 requestAnimationFrame 不传参,你拿到的是当前帧的时间戳(DOMHighResTimeStamp),不是固定间隔。如果每帧都加固定值(如 x += 2),那在 30fps 设备上就会慢一半。
正确做法是记录上一帧时间,算出本次耗时(delta),再按比例更新位移: