在前端开发中关于reflow(回流)和repaint(重绘)的几点思考
在前端开发中,reflow(回流)和repaint(重绘)是影响网页性能的两个关键概念,下面将对它们进行详细解释,并给出减少其发生次数的方法。
文章目录
- 什么是 Reflow(回流也叫重排)?
- 定义
- 触发 Reflow 的常见操作
- 什么是 Repaint(重绘)?
- 定义
- 触发 Repaint 的常见操作
- 两者关系
- 性能影响
- 如何减少 Reflow 和 Repaint 的次数?
- **批量修改 DOM**
- **使用 `requestAnimationFrame` 处理动画**
- **避免频繁读取布局信息**
- **开启 GPU 加速,使用 `transform` 和 `opacity` 进行动画**
- **将元素脱离文档流后进行修改**
- 使用position:absolute或position:fixed使元素脱离文档流修改样式
- **使用事件委托处理大量事件**
- **优化 CSS 加载顺序**
- 总结
什么是 Reflow(回流也叫重排)?
定义
Reflow 指的是浏览器为了重新计算文档中元素的布局信息(元素的大小、位置、边距等几何信息)而进行的过程。当 DOM 的变化影响了元素的布局信息时,浏览器需要重新计算元素在视口内的位置和尺寸,将其安放到界面中的正确位置,这个过程就被称为 Reflow。
通俗一点讲:无论通过什么方式影响了元素的几何信息(元素在视口内的位置和大小),浏览器都需要重新计算元素在视口内的几何属性,这个过程叫做Reflow(回流或重排)。
触发 Reflow 的常见操作
- 调整窗口大小(resize)
- 改变字体大小
- 添加或删除 DOM 元素
- 元素的尺寸(宽、高、边距等)发生变化
- 内容变化(文本数量或图片大小改变)
- 计算元素的某些属性(如
offsetWidth
、scrollTop
等)
什么是 Repaint(重绘)?
定义
Repaint 是指当一个元素的外观发生改变,但没有影响到布局信息时,浏览器将元素的新外观绘制到屏幕上的过程。Repaint 的开销通常比 Reflow 小,因为它只需处理元素的视觉样式,而不需要重新计算布局。
通俗一点讲:通过构造渲染树和重排(回流)阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(元素在视口内的位置和尺寸大小),接下来就可以将渲染树的每个节点转换为屏幕上的实际像素,这个阶段就叫做重绘。
触发 Repaint 的常见操作
- 改变元素的颜色、背景色
- 改变元素的可见性(
visibility
) - 改变元素的边框样式
- 改变元素的阴影效果
两者关系
- 回流一定会导致重绘,因为布局改变后外观必然变化;
- 重绘不一定导致回流,仅样式变化不影响布局时,只需重绘。
性能影响
回流的计算成本远高于重绘,频繁的回流会导致页面卡顿,开发中需要尽量减少(如避免频繁操作DOM样式,使用transform替代位移等).
如何减少 Reflow 和 Repaint 的次数?
为了优化网页性能,应尽量减少 Reflow 和 Repaint 的发生次数,特别是在处理动画、滚动等高频操作时。以下是一些实用的优化方法:
批量修改 DOM
不要频繁地修改 DOM,而是集中修改。例如:
// 低效做法
const el = document.getElementById('myElement');
el.style.width = '100px';
el.style.height = '200px';
el.style.margin = '10px';// 高效做法:合并样式修改
const el = document.getElementById('myElement');
el.classList.add('new-style'); // 预先定义好 new-style 类// 或者使用 CSSOM
const el = document.getElementById('myElement');
const style = el.style;
style.cssText += '; width: 100px; height: 200px; margin: 10px;';
使用 requestAnimationFrame
处理动画
对于动画效果,使用 requestAnimationFrame
可以将多次修改集中到一帧中执行,避免不必要的回流:
function animate() {requestAnimationFrame(() => {// 在这里进行 DOM 修改element.style.transform = 'translateX(100px)';element.style.opacity = '0.5';});
}
避免频繁读取布局信息
当读取元素的布局信息(如 offsetWidth
、scrollTop
等)时,浏览器会强制刷新布局,导致回流。因此,应避免在修改 DOM 的过程中频繁读取这些值:
// 低效做法:读取和修改交替进行
const el = document.getElementById('myElement');
console.log(el.offsetWidth); // 触发回流
el.style.width = '200px'; // 修改样式
console.log(el.offsetWidth); // 再次触发回流// 高效做法:批量读取,批量修改
const el = document.getElementById('myElement');
const width = el.offsetWidth; // 读取一次
el.style.width = '200px'; // 修改
el.style.height = '300px'; // 修改
// 后续再读取其他布局信息
开启 GPU 加速,使用 transform
和 opacity
进行动画
开启 GPU 加速,利用 css 属性 transform 、will-change 等,比如改变元素位置,我们使用 translate 会比使用绝对定位改变其 left 、top 等来的高效,因为它不会触发重排或重绘,transform 使浏览器为元素创建⼀个 GPU 图层,这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。
transform
和 opacity
不会触发回流和重绘,而是使用 GPU 进行加速,性能更好:
/* 使用 transform 代替 left/top 进行位置移动 */
.element {transition: transform 0.3s ease;
}
.element:hover {transform: translateX(50px);
}
将元素脱离文档流后进行修改
对于需要进行大量修改的元素,可以先将其脱离文档流,修改完成后再放回文档中,这样只会触发两次回流(脱离和放回时):
// 1. 隐藏元素
element.style.display = 'none';// 2. 进行多次修改
element.style.width = '200px';
element.style.height = '300px';
element.style.margin = '10px';// 3. 重新显示元素
element.style.display = 'block';
使用position:absolute或position:fixed使元素脱离文档流修改样式
默认情况下,HTML元素处于文档流中,它们的位置和大小会影响其他元素。使用position:absolute和position:fixed后,元素会脱离文档流,不再影响其他元素的布局。特别适合频繁动画或定位变动的元素,比如弹窗、浮动框、下拉菜单等。
注意:不能完全避免重排和重绘,初始设置absolute或fixed本身也需要一次重排,修改某些属性(如 width height top left)依然会触发重排和重绘,只是影响范围更小了。
使用事件委托处理大量事件
对于大量元素的事件处理,使用事件委托可以减少 DOM 操作,从而减少回流和重绘:
// 不推荐:为每个元素绑定事件
document.querySelectorAll('.item').forEach(item => {item.addEventListener('click', handleClick);
});// 推荐:使用事件委托
document.getElementById('container').addEventListener('click', (e) => {if (e.target.classList.contains('item')) {handleClick(e);}
});
优化 CSS 加载顺序
确保关键 CSS 尽早加载,避免因 CSS 加载延迟导致的回流。可以使用 rel="preload"
预加载重要的 CSS 文件:
<link rel="preload" href="critical.css" as="style" onload="this.rel='stylesheet'">
总结
Reflow 和 Repaint 是浏览器渲染过程中的必要步骤,但频繁触发会导致性能问题。通过合理规划 DOM 操作、优化 CSS 选择器、使用硬件加速等方法,可以有效减少 Reflow 和 Repaint 的次数,提升网页的响应速度和用户体验。