rAF 能避免掉帧是因为其回调在浏览器下一次重绘前执行,与屏幕刷新率同步,使 DOM 修改集中于单次渲染周期、减少重复 layout/paint,并支持批量处理样式计算与绘制。

在 JavaScript 中,直接频繁修改 DOM 容易触发强制同步布局(forced synchronous layout)和过多重排重绘,导致掉帧。利用 requestAnimationFrame(简称 rAF)把节点操作“对齐”到浏览器的渲染帧节奏中,是提升动画流畅性的关键手段。
为什么 rAF 能避免掉帧?
rAF 的回调会在浏览器下一次重绘前执行,天然与屏幕刷新率(通常是 60fps)同步。相比 setTimeout 或直接同步操作,它能确保:
- DOM 修改集中在单次渲染周期内完成,减少重复 layout/paint
- 避免在帧中间意外触发 layout(比如读取 offsetTop 后立刻改 class)
- 让浏览器有机会合并多个 DOM 变更,批量处理样式计算与绘制
正确使用 rAF 操作节点的三步法
核心原则:**读操作 → rAF → 写操作**,严格分离 layout 触发与 DOM 修改。
-
先读取所有需要的布局信息(如
offsetHeight、getBoundingClientRect()),不要在 rAF 回调里读——那会强制同步 layout - 用 rAF 包裹所有写操作(添加/删除/移动节点、修改 class、设置 style),确保它们在下一帧统一提交
- 避免在单次 rAF 中反复读写交替,例如不要「改 class → 读 offsetTop → 改 style → 读 clientWidth」
实际例子:平滑插入一批节点
错误做法(同步插入 + 强制 layout):
立即学习“Java免费学习笔记(深入)”;
items.forEach(item => {
const el = document.createElement('div');
el.textContent = item;
container.appendChild(el); // 每次都可能触发重排
console.log(el.offsetHeight); // 强制同步 layout → 掉帧风险高
});
优化后(rAF 批量 + 分离读写):
// 1. 先读(如果需要基于当前布局计算)
const containerRect = container.getBoundingClientRect();
// 2. 创建文档片段,暂存所有新节点
const fragment = document.createDocumentFragment();
items.forEach(item => {
const el = document.createElement('div');
el.textContent = item;
// 预设样式类或内联样式,避免后续回流
el.className = 'list-item';
fragment.appendChild(el);
});
// 3. 用 rAF 统一插入(写操作)
requestAnimationFrame(() => {
container.appendChild(fragment);
// 如需后续测量,也放在这里(此时已插入,但尚未渲染)
// const firstItem = container.firstElementChild;
// console.log(firstItem.offsetHeight); // 安全
});
进阶技巧:节流高频 DOM 更新
对于滚动、拖拽等持续触发的场景,可用 rAF 实现自动节流:
let isQueued = false;
function updateUI() {
if (isQueued) return;
isQueued = true;
requestAnimationFrame(() => {
// 执行实际 DOM 操作(如更新位置、显隐)
updatePosition();
updateVisibility();
isQueued = false;
});
}
// 在 scroll 或 mousemove 中调用
window.addEventListener('scroll', updateUI);
这样即使每毫秒触发 10 次,也只会每帧执行一次 DOM 更新,彻底避开掉帧。


















