回流是浏览器重新计算元素几何属性并构建渲染树的过程,重绘是仅重画外观变化的像素;回流必触发重绘,但重绘不一定触发回流;读取 offsetTop 等布局信息会强制同步回流,应批量读写分离,优先使用 transform/opacity,结合 DocumentFragment 和 display 隐藏优化。

什么是回流(reflow)和重绘(repaint)
回流是浏览器重新计算元素几何属性(位置、尺寸)并重新构建渲染树的过程;重绘是仅改变元素外观(如颜色、背景)但不触发几何变化时,重画像素的过程。回流必然触发重绘,但重绘不一定触发回流。offsetTop、clientWidth、getComputedStyle() 这类读取布局信息的 API 会强制同步触发回流——这是性能杀手的常见源头。
批量读写分离:避免强制同步回流
连续读写混用会导致多次回流。比如先读 offsetHeight,再改 style.width,再读 offsetLeft,浏览器会在每次读操作前“清空队列”,强制执行一次回流。
正确做法是把所有读操作集中到一起,所有写操作集中到一起:
const el = document.getElementById('box');
// ❌ 错误:读-写-读-写 → 触发 2 次回流
el.style.width = '200px';
console.log(el.offsetHeight);
el.style.height = '100px';
console.log(el.offsetWidth);
// ✅ 正确:读批量在前,写批量在后 → 最多 1 次回流
console.log(el.offsetHeight);
console.log(el.offsetWidth);
el.style.width = '200px';
el.style.height = '100px';
用 transform 和 opacity 替代位置/尺寸变更
CSS 属性中,transform(如 translateX、scale)和 opacity 属于合成层(compositing layer),修改它们只触发重绘或直接由 GPU 合成,完全跳过回流和主渲染管线。
- 用
transform: translateX(10px)代替left: 10px - 用
transform: scale(1.2)代替width/height变更 - 动画优先用
@keyframes+transform,而非 JS 修改top/marginLeft
注意:will-change: transform 可提前提示浏览器创建独立图层,但滥用会导致内存占用上升,只对明确频繁动画的元素设置。
离线 DOM 操作与文档片段
向 document.body 频繁插入多个节点(如循环 appendChild)会逐次触发回流。应先构建完整结构,再一次性挂载。
推荐方式:
- 用
DocumentFragment缓存节点,最后append()一次 - 对已有元素,先
el.style.display = 'none',操作完再设回'block' - 使用
innerHTML批量写入 HTML 字符串(比逐个创建元素快,但需注意 XSS)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
document.getElementById('list').appendChild(fragment); // 仅 1 次回流
真正难的是识别隐式回流——比如某个第三方库内部调用了 getBoundingClientRect(),或者框架响应式更新中不经意读写了布局属性。上线前用 Chrome DevTools 的 Rendering > Paint flashing 和 Layers 面板验证,比凭经验更可靠。











