requestAminationFrame 对比 setTimeout
说明:正确拼写是
requestAnimationFrame(很多人会把 Animation 拼错成 Amination)。本文后续统一用requestAnimationFrame。
面试速答(30 秒版 TL;DR)
setTimeout:把回调放进 宏任务(task)队列,到点后“尽快”执行,但会受到主线程繁忙、最小延迟钳制(clamp)、后台标签页节流等影响,所以 不适合做平滑动画。requestAnimationFrame(rAF):把回调放进 下一帧渲染前执行(浏览器的渲染时机驱动),天然与刷新率对齐,且在页面不可见时通常会暂停或强节流,适合 动画/逐帧更新。- 结论:做动画优先 rAF;做“延迟执行/超时兜底/退避重试”用
setTimeout。
心智模型:事件循环 + 渲染阶段里 rAF 的位置
把它记成一句话:setTimeout 属于“排队等 CPU”;requestAnimationFrame 属于“排队等下一次绘制”。
核心对比(面试高频点)
| 维度 | requestAnimationFrame | setTimeout |
|---|---|---|
| 驱动方式 | 渲染驱动,回调在下一帧绘制前触发 | 计时驱动,到期后进入任务队列等待执行 |
| 平滑度 | 更平滑,天然对齐刷新率 | 易抖动和掉帧,定时不准导致漂移 |
| 后台/不可见 | 通常暂停或强节流,节省电量 | 通常会被强节流(不同浏览器策略不同) |
| 参数 | 回调会收到高精度时间戳(DOMHighResTimeStamp) | 只有延迟毫秒数(并且会被钳制/节流) |
| 取消 | cancelAnimationFrame(id) | clearTimeout(id) |
| 典型用途 | 动画、逐帧更新、滚动/拖拽的视觉跟随 | 延迟执行、超时兜底、轮询(不推荐高频) |
为什么动画更推荐 rAF(追问题)
1) 刷新率对齐,避免“做了无用功”
屏幕一般是 60Hz/120Hz,浏览器通常只会在每帧绘制时把最新结果显示出来。你用 setTimeout(fn, 16) 即使“理论上 60fps”,也可能在一帧内触发多次或错过绘制窗口,造成抖动或浪费。
2) 时间基准更适合做动画插值
rAF 回调拿到时间戳,更适合用“按时间推进”的方式计算位移,而不是“每次 +1px”这种跟帧率强绑定的写法。
最小可运行示例:用 rAF 做基于时间的动画
const el = document.querySelector("#box");
let rafId = 0;
let startTs = 0;
function tick(ts) {
if (!startTs) startTs = ts;
const elapsed = ts - startTs;
// 2 秒从 0 移动到 300px
const duration = 2000;
const progress = Math.min(elapsed / duration, 1);
const x = 300 * progress;
el.style.transform = `translateX(${x}px)`;
if (progress < 1) {
rafId = requestAnimationFrame(tick);
}
}
rafId = requestAnimationFrame(tick);
// 需要取消时:
// cancelAnimationFrame(rafId);
对比一下,如果用 setTimeout 做动画,一般会遇到:
- 主线程忙时回调延后,帧间隔不稳定。
setTimeout(..., 0)也不等于立刻执行(会被最小延迟钳制)。- 长时间运行会“漂移”(误差累计),尤其是用“固定步长”更新时更明显。
典型题 & 标准答法
Q1:用 setTimeout 能不能写动画?
能,但不推荐。标准回答是:动画本质要与“渲染时机”对齐,requestAnimationFrame 专门为此设计;setTimeout 只保证“延迟入队”,无法保证与每帧绘制同步,且在后台会被强节流,导致不稳定。
Q2:requestAnimationFrame 的回调一定是 16.6ms 一次吗?
不一定。它的触发频率受刷新率、页面负载、浏览器调度策略影响;掉帧时回调会变慢,所以应该用回调时间戳做基于时间的推进,而不是假设固定帧间隔。
易错点/坑
- rAF 里读写布局要小心:频繁触发布局(layout)会让每帧更慢,反而掉帧。建议把“读布局”和“写样式”分开,避免强制同步布局。
- 不要用
setTimeout当“精准计时器”:它更像“到点后尽快执行”,在后台/省电模式/高负载下误差会很大(不同浏览器策略不同)。 - 拼写坑:
requestAminationFrame是错的,正确是requestAnimationFrame。
速记要点(可背诵)
- 动画用 rAF:渲染驱动、对齐每帧绘制、拿到时间戳更好做插值。
- 延迟/超时用
setTimeout:计时驱动,但不保证准时且会被钳制/节流。