requestAnimationFrame 本身不卡顿,卡顿源于回调中执行大量DOM查询、强制同步布局、未节流事件、频繁变更动画目标等错误用法;它与VSync对齐、后台暂停、提供高精度时间戳,但无法避免重排重绘;应优先用CSS动画,仅在需精确帧控(如物理模拟)时使用,并依赖time参数而非固定帧率假设。
requestAnimationFrame 是浏览器原生提供的、与屏幕刷新率同步的回调机制,理论帧率可达 60fps(16.67ms/帧)。它本身没有性能开销,也不“导致”卡顿——卡顿永远来自你塞进去的逻辑。常见错误包括:
document.getElementById)offsetTop 后立刻改 style.left)scroll 中不断调用 requestAnimationFrame 却不防抖)和 setTimeout(fn, 16) 相比,requestAnimationFrame 的优势在于:
raf(time) 中的 time 是高精度单调递增时间)transform 和 opacity,那大概率顺滑;如果靠修改 top/left + position: relative,再用 requestAnimationFrame 也救不回来。
最典型的卡顿组合是:
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);
简单淡入、位移动画,CSS @keyframes + will-change: transform 更轻量;
需要响应鼠标轨迹、物理模拟、或与音频/传感器时间轴对齐时,requestAnimationFrame 才不可替代。
容易被忽略的一点是:它不保证“每帧都执行”,比如设备性能不足、页面后台运行、或显示器刷新率动态切换(如 MacBook Pro 的 ProMotion),time 参数才是你做插值的唯一可信依据,别硬写 if (frameCount++ % 3 === 0) 这类假设固定帧率的逻辑。