在高性能前端开发中,浏览器的渲染机制是绕不开的话题。Reflow(回流)与 Repaint(重绘)是性能优化的关键节点,但很多开发者对它们的认知停留在表面:“操作 DOM 多了会卡”、“不要频繁修改样式”……但为什么会卡?哪些操作一定会触发回流?有没有浏览器的优化机制?这篇文章,我们系统解析这些问题。
一、什么是回流与重绘?
浏览器渲染页面的核心流程为:
HTML 解析 → DOM 树构建 → CSS 解析 → CSSOM 构建 → 合并生成 Render Tree → Layout(回流)→ Paint(重绘)→ Composite(合成)
其中:
- 回流(Reflow 或 Layout) :当元素的尺寸、结构或位置发生变化,浏览器需要重新计算部分或全部的渲染树的几何信息(位置和大小)。
- 重绘(Repaint) :当元素的样式发生改变,但不影响几何结构(如颜色、背景、阴影等),只需重新绘制外观。
举个例子:
| 操作 | 是否引起回流 | 是否引起重绘 |
|---|---|---|
改变元素的 width | ✅ 是 | ✅ 是 |
改变元素的 color | ❌ 否 | ✅ 是 |
修改 display | ✅ 是 | ✅ 是 |
修改 visibility | ❌ 否 | ✅ 是 |
改变 font-size | ✅ 是 | ✅ 是 |
| 滚动页面 | ✅ 是 | ✅ 是 |
改变 transform | ❌ 否(GPU) | ✅(合成层) |
二、回流的代价有多高?
为什么回流更“贵”?
回流是一个自底向上的操作,涉及:
- 重新计算元素及其所有子元素的位置和大小
- 可能触发 Render Tree 的部分或全部更新
- 严重时可能导致 多次回流嵌套触发,例如频繁的 DOM 操作 + 样式读取写入混杂
这会引起性能问题,尤其在低性能设备上表现明显。
三、哪些操作会触发回流?
1. 直接操作样式相关属性
如:
el.style.width = '100px'
el.style.padding = '10px'
2. 改变 DOM 结构
如:
parent.appendChild(child)
parent.removeChild(child)
3. 读取触发同步计算的属性
浏览器为优化性能,会延迟布局计算(Lazy Layout) 。但你一旦访问下列属性,浏览器会强制刷新回流队列并立即计算布局:
el.offsetHeight
el.offsetTop
el.scrollHeight
el.clientWidth
getComputedStyle(el).marginTop
这类操作叫做强制同步布局(Forced Synchronous Layout) 。
4. 改变字体、窗口尺寸、媒体查询变化等
四、浏览器优化机制
现代浏览器使用了如下机制来降低回流和重绘的频率:
✅ 批处理机制(Batching)
DOM 修改并不是立即渲染,浏览器会尽可能合并操作,例如使用 requestAnimationFrame 将多次修改合并在同一帧。
✅ 分层(Layering)
如 transform 和 will-change 会把元素提升为合成层,避免父元素重排的连锁影响。
五、性能优化策略
1. 避免频繁操作 DOM
错误:
for (let i = 0; i < 100; i++) {
const el = document.createElement('div');
document.body.appendChild(el);
}
优化:
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const el = document.createElement('div');
fragment.appendChild(el);
}
document.body.appendChild(fragment);
2. 样式修改集中处理
错误:
el.style.width = '100px';
el.style.padding = '10px';
el.style.margin = '20px';
优化:
el.style.cssText = 'width: 100px; padding: 10px; margin: 20px;';
3. 读写分离(Avoid Layout Thrashing)
避免读写交叉:
// ❌ layout thrashing
el.style.height = el.offsetHeight + 10 + 'px';
优化方式:先读后写:
js
复制编辑
const height = el.offsetHeight;
el.style.height = height + 10 + 'px';
4. 使用 requestAnimationFrame 管理动画
让 DOM 操作在下一帧执行,浏览器可批量处理,避免多次回流:
requestAnimationFrame(() => {
el.style.left = '100px';
});
5. 使用虚拟列表技术(如:React 虚拟 DOM / Vue 虚拟列表)
用于长列表渲染,减少 DOM 数量,降低回流成本。
六、如何监控回流与重绘
1. Chrome DevTools
- 打开 Performance 面板,点击“Record”
- 可以看到
Recalculate Style、Layout、Paint的时间消耗
2. Layout Shift 指标(LCP + CLS)
- Core Web Vitals 中的 CLS(Cumulative Layout Shift)直接衡量回流引发的用户体验抖动
七、总结
| 关键词 | 回流(Reflow) | 重绘(Repaint) |
|---|---|---|
| 触发条件 | 几何结构改变 | 外观样式改变 |
| 代价 | 高,影响布局树 | 相对低 |
| 常见操作 | DOM结构变化、宽高、字体 | 背景色、字体颜色 |
| 性能优化 | 批量操作、分离读写、虚拟 DOM | 合成层、避免不必要样式变动 |
回流与重绘并不是“禁区”,但理解它们的原理与成本,是进行高性能前端开发的基础。用数据驱动判断、用工具辅助调试、用架构优化结构,才能在性能和体验之间实现良好平衡。
让你的网站“飞起来”,不是靠魔法,而是靠你对浏览器机制的精细掌控。