requestAnimationFrame 本身不卡顿,卡顿源于回调中执行大量DOM查询、强制同步布局、未节流事件、频繁变更动画目标等错误用法;它与VSync对齐、后台暂停、提供高精度时间戳,但无法避免重排重绘;应优先用CSS动画,仅在需精确帧控(如物理模拟)时使用,并依赖time参数而非固定帧率假设。

requestAnimationFrame 本身不卡顿,但用法不对就卡
requestAnimationFrame 是浏览器原生提供的、与屏幕刷新率同步的回调机制,理论帧率可达 60fps(16.67ms/帧)。它本身没有性能开销,也不“导致”卡顿——卡顿永远来自你塞进去的逻辑。常见错误包括:
- 在回调里执行大量 DOM 查询(如反复调用
document.getElementById) - 触发强制同步布局(例如读取
offsetTop后立刻改style.left) - 未节流的事件监听器(如
scroll中不断调用requestAnimationFrame却不防抖) - 动画目标频繁变更却未取消上一帧(导致多帧并发更新同一元素)
比 setTimeout 顺滑,但不是万能解药
和 setTimeout(fn, 16) 相比,requestAnimationFrame 的优势在于:
- 浏览器可将其调度与垂直同步(VSync)对齐,避免丢帧
- 页面非激活时自动暂停(标签页切走后不耗资源)
- 支持更精准的时间戳(
raf(time)中的time是高精度单调递增时间)
但它不会帮你优化重排重绘。如果你动画依赖
transform 和 opacity,那大概率顺滑;如果靠修改 top/left + position: relative,再用 requestAnimationFrame 也救不回来。
真实卡顿场景:布局抖动 + 频繁 GC
最典型的卡顿组合是:
- 每帧读取
element.getBoundingClientRect()(触发重排) - 紧接着设置
element.style.transform = ...(触发重绘) - 又在下一帧重复——形成“读-写-读-写”循环,强制浏览器每帧都做布局计算
- 再加上每次创建新对象(如
{ x: ..., y: ... })未复用,引发频繁垃圾回收(GC)
这种情况下,即使用了
requestAnimationFrame,Performance 面板也会显示大量黄色的 “Layout” 和 “Recalculate Style” 块。
let lastX = 0;
const el = document.querySelector('.box');
function animate() {
// ✅ 安全:只读一次 layout,且缓存
const rect = el.getBoundingClientRect();
const x = rect.left + 2;
// ✅ 安全:仅用 transform,触发合成层
el.style.transform = `translateX(${x}px)`;
// ❌ 危险:如果这里加一句 console.log(el.offsetTop),就会强制同步布局
lastX = x;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
要不要用 requestAnimationFrame?看你的动画是否需要精确帧控
简单淡入、位移动画,CSS @keyframes + will-change: transform 更轻量;
需要响应鼠标轨迹、物理模拟、或与音频/传感器时间轴对齐时,requestAnimationFrame 才不可替代。
容易被忽略的一点是:它不保证“每帧都执行”,比如设备性能不足、页面后台运行、或显示器刷新率动态切换(如 MacBook Pro 的 ProMotion),time 参数才是你做插值的唯一可信依据,别硬写 if (frameCount++ % 3 === 0) 这类假设固定帧率的逻辑。
立即学习“前端免费学习笔记(深入)”;











