requestAnimationFrame 更适合动画因其与屏幕刷新同步、自动暂停后台标签页且需递归驱动+退出控制;setTimeout 则易不同步、不暂停且易致卡顿或资源浪费。

requestAnimationFrame 为什么比 setTimeout 更适合动画
因为浏览器会把 requestAnimationFrame 调用对齐到屏幕刷新节奏(通常是 60Hz),避免丢帧、卡顿或“撕裂”;而 setTimeout 是基于时间间隔硬调度,容易和刷新不同步,尤其在页面后台运行或 CPU 压力大时,会累积延迟或突然跳变。
更关键的是:requestAnimationFrame 在标签页不可见时自动暂停,不浪费资源;setTimeout 不会——你可能还在后台疯狂计算,但用户根本看不见。
最简可用的 requestAnimationFrame 动画结构
核心不是“启动”,而是“递归驱动 + 退出控制”。漏掉退出逻辑会导致内存泄漏和持续占用主线程。
- 必须用一个变量(如
animationId)保存上一次requestAnimationFrame的返回值 - 每次回调中先做动画更新(如修改
element.style.transform),再根据条件决定是否继续调用requestAnimationFrame - 停止动画时,必须调用
cancelAnimationFrame(animationId),否则回调会一直注册下去
let animationId = null;
const animate = () => {
// 更新位置/样式等
element.style.transform = `translateX(${x}px)`;
if (x < 100) {
x += 2;
animationId = requestAnimationFrame(animate);
}
};
animationId = requestAnimationFrame(animate);
// 停止时:
cancelAnimationFrame(animationId);
常见错误:直接在循环里反复调 requestAnimationFrame
比如写成 for (let i = 0; i —— 这会瞬间注册 100 个独立回调,全部在下一帧执行,不仅毫无渐进效果,还会阻塞渲染、触发强制同步布局。
立即学习“前端免费学习笔记(深入)”;
真正需要的是单次注册 → 执行 → 条件判断 → 再注册(可选)。动画是“一帧一帧推进”的过程,不是“一次性批量提交”。
- 不要在事件处理器(如
click)里无防护地多次调用requestAnimationFrame - 避免在
scroll或resize中直接调用,应加节流或用IntersectionObserver替代 - 如果需多元素同步动画,复用同一个
requestAnimationFrame回调,在内部遍历更新,而不是为每个元素单独注册
和 CSS 动画/transition 比,什么时候该选 requestAnimationFrame
当动画逻辑依赖运行时数据、需要逐帧干预(如跟随鼠标、物理模拟、滚动视差),或者要精确控制暂停/倒放/变速时,requestAnimationFrame 是唯一选择。CSS 的 transition 和 @keyframes 无法响应式更新缓动参数或中间状态。
但要注意:纯位移、缩放、透明度这类简单变换,优先用 CSS(开启 will-change: transform 或确保只触发合成),它走的是合成线程,不经过主线程布局与绘制,性能远高于 JS 操作 style。
混合使用也常见:用 requestAnimationFrame 计算目标值,再通过 element.animate()(Web Animations API)交由浏览器优化执行——这样既保逻辑灵活,又享底层优化。



















