requestAnimationFrame 和 setTimeout / setInterval 有什么区别?为什么页面动画优先用 rAF,而倒计时 / 轮询有时反而用 setTimeout?
三者的本质区别
| 维度 | requestAnimationFrame | setTimeout | setInterval |
|---|---|---|---|
| 触发时机 | 浏览器每次重绘前(与刷新率同步,通常 60fps) | 至少等待指定毫秒后(宏任务) | 每隔指定毫秒(宏任务) |
| 后台标签页 | 自动暂停(省电、不空转) | 被节流到 >=1s | 被节流到 >=1s |
| 精度 | 与渲染帧对齐,动画最顺滑 | 受事件循环阻塞、易抖动 | 同上,且可能「丢帧堆叠」 |
| 适合场景 | 视觉动画、滚动、逐帧渲染 | 单次延时、轮询、倒计时 | 固定间隔重复(不推荐做动画) |
为什么动画优先用 rAF
// ✅ 平滑动画:每帧根据时间戳计算进度,与屏幕刷新同步
function animate(el) {
let start
function step(ts) {
if (!start) start = ts
const p = (ts - start) / 1000 // 已过去秒数
el.style.transform = 'translateX(' + (p * 200) + 'px)'
if (p < 1) requestAnimationFrame(step)
}
requestAnimationFrame(step)
}如果用 setInterval(fn, 16),定时器与渲染帧不一定对齐,容易掉帧卡顿,且在后台标签页仍持续运行浪费资源。
为什么倒计时 / 轮询有时用 setTimeout
- 打字机效果:希望「每隔固定字符间隔」出现下一个字,是时间驱动而非帧驱动,用
setInterval更直观。 - 倒计时器:用
setTimeout递归可做到切到后台仍在计时(rAF 会暂停),适合真实计时场景(如考试倒计时)。 - 轮询:用
setTimeout递归(上一次请求回来再排下一次)比setInterval更安全,避免请求堆积。
关键判断:视觉动画 → rAF;真实时间 / 轮询 / 与帧无关的节奏 → setTimeout / setInterval。
注意事项
setInterval回调执行时间若超过间隔,回调会「堆叠」排队,可能导致逻辑错乱,业务轮询建议用递归setTimeout。- 后台标签页中
setTimeout/setInterval最小间隔被浏览器限制为 >=1000ms。