requestAnimationFrame 和 setTimeout 有什么区别?为什么动画不应该固定每帧移动几像素?
下面是一段教学用的模拟面试。
🧑💻 面试官:页面动画用 setTimeout 每隔 16 毫秒移动一次,可以吗?
🙋♂️ 我:可以做,但定时器不一定准时,也不一定对上屏幕刷新。
🧑💻 面试官:那换成 requestAnimationFrame,每次移动 5 像素呢?
🙋♂️ 我:高刷新率屏幕回调更多,可能会移动得更快。
🧑💻 面试官:怎么让动画在不同屏幕上都用一秒完成?标签页切到后台再回来,又应该显示哪里?
先确定动画的「时间进度」,再安排什么时候更新画面。回调次数不能代替真实经过时间。
面试速答(60 秒版)
setTimeout 表示经过指定延迟后,回调有机会被调度,不承诺准确在那个时间执行。requestAnimationFrame 则让浏览器在一次合适的绘制前执行回调,更适合更新动画画面。
rAF 通常跟着屏幕刷新节奏运行,不固定等于每 16 毫秒一次,而且只安排一轮,下一轮要再次请求。后台页面通常会暂停这类回调。
动画应该根据回调提供的时间戳计算进度,而不是每次固定移动几像素。这样即使回调间隔变化,同一个时间点也能得到同一个位置。至于后台回来要跳到当前进度,还是从暂停处继续,需要根据产品要求设计时间基准,不能让调度行为替你决定。

图:动画跟着时间,不跟着次数。
知识点详解:让一个小球在一秒内移动 200 像素
固定步长,为什么会跟着屏幕变快?
假设咱们每收到一轮回调,就把小球向右移动 5 像素。完成 200 像素需要 40 轮。屏幕刷新更快,40 轮可能更早到来;页面很忙,40 轮也可能拖很久。
问题不在于 5 像素选得不好,而在于这段代码用「次数」定义速度。改成根据经过时间计算,移动关系就清楚了:经过 500 毫秒,走一半;经过 1000 毫秒,到终点。

图:每次走一样远,速度就会变。
时间戳怎样参与计算?
把第一轮时间保存为起点,每轮算出经过时长,再除以总时长。进度超过 1 时截到 1,避免越过终点。rAF 文档说明了回调时间戳与不同刷新率下的注意事项。
下面是浏览器 TypeScript 示例。Python 没有同一浏览器帧 API;服务端定时器不能充当它的对应实现。
TypeScript
function move(ball: HTMLElement): () => void {
let start: number | undefined;
let frameId = 0;
function frame(now: number) {
start ??= now;
const progress = Math.min((now - start) / 1000, 1);
ball.style.transform = 'translateX(' + 200 * progress + 'px)';
if (progress < 1) frameId = requestAnimationFrame(frame);
}
frameId = requestAnimationFrame(frame);
return () => cancelAnimationFrame(frameId);
}
返回的函数用于取消尚未执行的回调。组件退出时调用它,可以避免动画继续操作不再需要的元素。这里演示的是线性运动,不是通用动画库。
用 rAF 就不会卡了吗?
也不会。回调中的工作仍需要完成。如果一轮里计算很久、不断读写布局,浏览器可能错过绘制机会。rAF 帮你选择更新时机,没有增加主线程的处理能力。
因此,复杂动画可以优先考虑 CSS 动画或 Web Animations;确实需要逐帧计算,再使用 rAF。测量时看每轮耗时、掉帧和主线程长任务,不只看调用了哪个 API。
后台回来,时间应不应该继续算?
上面的例子保留第一轮起点,回到前台后会根据经过的时间直接计算位置,可能立即到终点。倒计时或按绝对时间展示的进度,可以需要这种行为。
如果是必须暂停的交互动画,则要记录暂停时长,从总经过时间里扣掉。两种行为都可能正确,但要先把需求说清楚。验证时除了正常播放,还要切后台、取消和重复启动。
面试官继续追问
rAF 是绘制完成后的通知吗?
不是,它用于安排绘制前的更新机会。不能把回调执行理解成用户已经看到了这一帧。
回调里再请求一次,会在同一帧不断执行吗?
不会变成一个同步循环;下一次请求等待后续的回调机会。仍要控制每轮工作量。
所有延迟任务都应该换成 rAF 吗?
不是。后台巡检、请求超时等不依赖显示节奏,而且后台暂停可能违背需求。按任务目的选择,不按名字先进程度选择。
面试速记卡
- 定时器:延迟后获得调度机会,不保证准点。
- rAF:配合绘制节奏,一次请求只安排一轮。
- 进度:根据时间戳计算,不根据回调次数计算。
- 性能:回调太重仍会掉帧,调度不是加速计算。
- 后台:继续计时还是暂停,要明确时间模型。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →