requestAnimationFrame是专为动画设计的帧同步机制,通过递归调用实现流畅节能的视觉更新,需用cancelAnimationFrame控制启停,并利用回调timestamp实现时间驱动动画;非视觉任务仍应使用setTimeout/setInterval。

requestAnimationFrame 不是用来“替代定时器”的工具,而是为动画而生的专用机制——它不追求固定毫秒间隔,而是让浏览器在下一次重绘前执行回调,从而实现与屏幕刷新率同步、更流畅、更节能的视觉更新。
核心用法:基础循环结构
它本身只调用一次,要实现持续动画,需在回调中递归调用自己:
function animate() {
// 更新动画状态(如位置、透明度等)
element.style.transform = `translateX(${x}px)`;
<p>// 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);注意:不要用 setInterval(animate, 16) 或类似方式“模拟”,那会失去帧同步优势,还可能造成丢帧或卡顿。
控制启停:避免内存泄漏和无效运行
必须保存返回的 ID,并在不需要时取消:
立即学习“前端免费学习笔记(深入)”;
- 启动时存 ID:
let animationId = requestAnimationFrame(animate); - 暂停/停止时调用:
cancelAnimationFrame(animationId); - 常见场景:页面切到后台、元素不可见、用户暂停动画时主动取消
未取消的 rAF 会持续触发,即使页面不可见,也可能导致性能浪费或逻辑错乱。
时间精度:用 timestamp 做真正的时间驱动
rAF 回调会传入一个高精度时间戳(DOMHighResTimeStamp),单位是毫秒,精确到微秒,比 Date.now() 更可靠:
let startTime = performance.now();
function animate(timestamp) {
const elapsed = timestamp - startTime;
const progress = (elapsed % 2000) / 2000; // 每2秒循环一次
element.style.opacity = Math.sin(progress * Math.PI * 2) * 0.5 + 0.5;
<p>requestAnimationFrame(animate);
}
requestAnimationFrame(animate);这样写出来的动画不受帧率波动影响——60fps 或 30fps 下,运动节奏都一致。
不是所有轮询都该换:什么情况仍用 setTimeout/setInterval
- 非视觉任务:比如轮询接口、倒计时显示(需精确到秒)、节流防抖逻辑
- 低频更新(如每5秒检查一次状态):rAF 每秒触发60次,纯属浪费
- 需要保证最小延迟的任务(如快速响应用户输入):rAF 可能延迟一帧,而
setTimeout(fn, 0)在宏任务队列中更及时
混淆使用反而增加复杂度,也违背浏览器调度本意。



















