跳到主要内容

requestAminationFrame 对比 setTimeout

说明:正确拼写是 requestAnimationFrame(很多人会把 Animation 拼错成 Amination)。本文后续统一用 requestAnimationFrame

面试速答(30 秒版 TL;DR)

  • setTimeout:把回调放进 宏任务(task)队列,到点后“尽快”执行,但会受到主线程繁忙、最小延迟钳制(clamp)、后台标签页节流等影响,所以 不适合做平滑动画
  • requestAnimationFrame(rAF):把回调放进 下一帧渲染前执行(浏览器的渲染时机驱动),天然与刷新率对齐,且在页面不可见时通常会暂停或强节流,适合 动画/逐帧更新
  • 结论:做动画优先 rAF;做“延迟执行/超时兜底/退避重试”用 setTimeout

心智模型:事件循环 + 渲染阶段里 rAF 的位置

把它记成一句话:setTimeout 属于“排队等 CPU”;requestAnimationFrame 属于“排队等下一次绘制”。


核心对比(面试高频点)

维度requestAnimationFramesetTimeout
驱动方式渲染驱动,回调在下一帧绘制前触发计时驱动,到期后进入任务队列等待执行
平滑度更平滑,天然对齐刷新率易抖动和掉帧,定时不准导致漂移
后台/不可见通常暂停或强节流,节省电量通常会被强节流(不同浏览器策略不同)
参数回调会收到高精度时间戳(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:计时驱动,但不保证准时且会被钳制/节流。