在浏览器渲染页面的过程中,回流和重绘是两个核心的渲染环节,二者的触发机制和性能开销存在明显差异,理解这些差异是优化网页性能的重要基础。

什么是回流
回流也叫重排,指的是当渲染树中元素的布局属性发生变化,导致浏览器需要重新计算元素的位置、大小等几何信息,然后重新构建渲染树的过程。回流的触发会涉及整个渲染流程的重新计算,性能开销相对较大。
常见的触发回流的操作包括:
- 修改元素的宽高、边距、定位等几何属性
- 添加或删除可见的DOM元素
- 浏览器窗口尺寸发生变化
- 读取某些会触发回流的属性,比如
offsetWidth、offsetHeight、clientTop等 - 激活CSS的伪类,比如
:hover
什么是重绘
重绘指的是当元素的外观属性发生变化,但不影响其在文档流中的位置和尺寸时,浏览器只需要重新绘制元素的外观,不需要重新计算布局的过程。重绘的性能开销比重流小很多。
常见的触发重绘的操作包括:
- 修改元素的颜色、背景色、边框颜色等外观属性
- 修改元素的visibility属性为visible或hidden
- 修改元素的outline属性
回流和重绘的核心差异
二者的核心区别主要体现在触发条件、性能开销和影响范围三个方面,具体对比如下:
| 对比维度 | 回流 | 重绘 |
|---|---|---|
| 触发条件 | 元素几何属性或布局发生变化 | 元素外观属性发生变化,不影响布局 |
| 性能开销 | 较大,需要重新计算布局和渲染树 | 较小,仅重新绘制元素外观 |
| 影响范围 | 可能影响所有相关元素的布局 | 仅影响当前元素的外观 |
| 是否必然触发对方 | 回流一定会触发重绘 | 重绘不会触发回流 |
基于回流重绘的性能优化方案
减少回流次数
可以通过以下方式减少不必要的回流:
- 避免频繁操作样式,尽量将多次样式修改合并为一次,比如使用
class批量修改样式,而不是逐行修改style属性 - 需要多次操作DOM时,先将元素脱离文档流,操作完成后再放回,比如使用
display:none隐藏元素后操作,或者使用文档片段DocumentFragment - 避免频繁读取会触发回流的属性,如果需要多次使用这类属性,可以先将其缓存到变量中
下面是使用文档片段减少回流的示例代码:
// 创建文档片段
const fragment = document.createDocumentFragment();
// 循环创建10个li元素并添加到片段中
for (let i = 0; i < 10; i++) {
const li = document.createElement('li');
li.textContent = `列表项${i + 1}`;
fragment.appendChild(li);
}
// 一次性将片段添加到ul中,只触发一次回流
const ul = document.querySelector('ul');
ul.appendChild(fragment);
减少重绘次数
减少重绘的核心思路是避免不必要的外观修改,比如:
- 尽量使用CSS3的transform和opacity属性实现动画,这两个属性不会触发回流和重绘,只会触发合成层操作,性能更好
- 避免使用table布局,因为table中某个元素的变化可能会触发整个table的回流
- 对于需要频繁修改外观的元素,可以开启GPU加速,将其提升为独立的合成层
下面是使用transform实现动画的示例代码:
/* 使用transform实现位移动画,不会触发回流重绘 */
.animate-box {
width: 100px;
height: 100px;
background: #409eff;
transition: transform 0.3s ease;
}
.animate-box:hover {
transform: translateX(50px);
}
其他优化建议
- 简化CSS选择器,减少渲染树匹配元素的时间
- 避免设置过多的内联样式,尽量使用外部样式表
- 对于复杂的动画,可以使用requestAnimationFrame来调度,保证动画和浏览器刷新频率同步
合理利用上述优化方案,可以有效减少回流和重绘的次数,降低页面的渲染开销,从而提升网页的整体性能和用户体验。