深入理解浏览器的回流(Reflow)与重绘(Repaint):原理、性能与优化策略

526 阅读4分钟

在高性能前端开发中,浏览器的渲染机制是绕不开的话题。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)

transformwill-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 StyleLayoutPaint 的时间消耗

2. Layout Shift 指标(LCP + CLS)

  • Core Web Vitals 中的 CLS(Cumulative Layout Shift)直接衡量回流引发的用户体验抖动

七、总结

关键词回流(Reflow)重绘(Repaint)
触发条件几何结构改变外观样式改变
代价高,影响布局树相对低
常见操作DOM结构变化、宽高、字体背景色、字体颜色
性能优化批量操作、分离读写、虚拟 DOM合成层、避免不必要样式变动

回流与重绘并不是“禁区”,但理解它们的原理与成本,是进行高性能前端开发的基础。用数据驱动判断、用工具辅助调试、用架构优化结构,才能在性能和体验之间实现良好平衡。


让你的网站“飞起来”,不是靠魔法,而是靠你对浏览器机制的精细掌控。