回流(reflow)和 重绘(repaint)是浏览器渲染过程中的两个关键概念。
一、概念:
- 回流指的是浏览器在计算文档流布局(layout)时,重新计算元素的位置和大小的过程。当页面中的元素发生尺寸变化、添加或删除元素、文本内容变化等操作时,会触发回流。回流可能会导致其他元素的位置和大小也需要重新计算,这可能是一个比较耗费性能的操作。
- 重绘是指在回流之后,浏览器将更新后的元素样式绘制到屏幕上的过程。当元素的外观属性(如颜色、背景等)发生变化时,会触发重绘。重绘不涉及布局的计算,所以性能上相对来说较回流开销较小。
具体的浏览器解析渲染机制如下所示:
-
解析HTML,生成DOM树,解析CSS,生成CSSOM树
-
将DOM树和CSSOM树结合,生成渲染树(Render Tree)
-
Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
-
Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
-
Display:将像素发送给GPU,展示在页面上
在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变;
当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来;
当我们对 DOM 的修改导致了样式的变化(color或background-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了重绘。
二、示例:
当涉及到回流和重绘时,以下是一些常见的例子:
- 修改元素的尺寸:例如,通过 JavaScript 动态修改元素的宽度或高度。这会触发回流,因为浏览器需要重新计算元素的布局,并随后进行重绘。
// 触发回流和重绘
element.style.width = "200px";
element.style.height = "100px";
- 改变字体属性:如果在 CSS 中改变了元素的字体大小、字体样式等属性,将会触发回流和重绘。因为文本的尺寸变化可能会导致周围元素的位置和大小发生变化。
/* 触发回流和重绘 */
.element {
font-size: 16px;
font-weight: bold;
}
- 添加或删除元素:当通过 JavaScript 动态添加或删除元素时,会触发回流和重绘。因为浏览器需要重新计算文档流的布局,并更新显示。
// 触发回流和重绘
var newElement = document.createElement("div");
parentElement.appendChild(newElement);
// 触发回流和重绘
var elementToRemove = document.getElementById("elementId");
parentElement.removeChild(elementToRemove);
- 修改元素的位置:如果通过修改元素的定位属性(如 top、left)来改变其位置,会触发回流和重绘。因为周围元素的布局可能会受到影响。
// 触发回流和重绘
element.style.position = "absolute";
element.style.top = "50px";
element.style.left = "100px";
- 改变元素的背景色或边框样式:当通过 CSS 修改元素的背景色、边框样式等外观属性时,将触发重绘。这不会引起回流,因为这些属性的更改不会影响布局。
/* 触发重绘,不触发回流 */
.element {
background-color: red;
border: 1px solid black;
}
需要注意的是,不同浏览器对回流和重绘的处理方式可能略有差异。此外,现代浏览器通常会对频繁的回流进行优化,尽量减少性能开销。然而,过多的回流和重绘仍然可能导致页面性能下降,因此在编写代码时应尽量避免不必要的操作。
三、理解回流和重绘的关系可以简单如下:
-
回流必然伴随着重绘:当元素的位置和大小发生改变时,不仅需要进行回流计算,还需要进行重绘操作。所以每次回流都会导致重绘。
-
重绘不一定会引起回流:当元素的外观属性发生改变时,只需要进行重绘而无需重新计算布局,因此不一定会引起回流。比如改变了元素的颜色、背景等样式属性。
由于回流涉及到布局的计算,因此它的性能开销较大,会导致页面重新渲染,影响用户体验和页面性能。所以在开发过程中应尽量避免频繁的回流操作,可以采取以下措施:
- 批量修改样式:将多个样式的修改集中到一起,通过添加或移除 CSS 类来实现样式的批量更改,减少回流次数。
- 避免强制同步布局:典型的例子是查询某些布局属性(如 offsetTop、offsetWidth 等)时,浏览器会强制进行回流以保证获取到最新的值。如果无法避免,可以尽量减少这类操作的次数。
- 使用 transform 替代 top/left:使用 CSS3 的 transform 属性进行位移操作,能够避免回流,并有较好的性能表现。
总之,了解回流和重绘的原理能够帮助我们优化代码,减少不必要的性能开销,提升页面的渲染性能。
四、下面是减少回流和重绘的方法的一些优点和缺点:
优点:
- 提高页面性能:减少回流和重绘可以减少浏览器的计算和渲染工作量,从而提高页面的性能和响应速度。
- 减少资源消耗:回流和重绘会消耗 CPU 和 GPU 资源,通过减少它们的次数,可以降低系统资源的使用,延长电池寿命等。
- 改善用户体验:减少回流和重绘可以减少页面加载和渲染的延迟,提高用户与网页的交互体验。
缺点:
- 可能增加代码复杂性:为了减少回流和重绘,可能需要对代码进行修改,引入额外的逻辑,从而增加代码的复杂性和维护成本。
- 可能影响开发效率:在追求性能最佳实践的同时,可能需要投入更多的时间和精力来进行代码优化和测试。
- 不适用于所有情况:有时候,为了实现特定的功能或效果,可能无法完全避免回流和重绘。在这种情况下,需要权衡性能和功能之间的平衡。
综上所述,减少回流和重绘可以有效提高页面性能和用户体验,但需要权衡代码复杂性和开发效率。在实际开发中,需要根据具体情况做出决策,选择适合的优化方法。
五、如何减少回流和重绘呢?
- 批量修改样式:尽量避免多次修改单个元素的样式,而是通过添加或移除 CSS 类来一次性修改多个样式。这样可以限制回流和重绘的次数。
// 不推荐的方式(多次回流和重绘)
element.style.width = "200px";
element.style.height = "100px";
// 推荐的方式(一次回流和重绘)
element.classList.add("new-styles");
- 使用 transform 替代定位属性:在改变元素的位置时,使用 transform 属性代替 top/left 属性。因为 transform 属性只会触发重绘,而不会触发回流。
// 不推荐的方式(触发回流和重绘)
element.style.position = "absolute";
element.style.top = "50px";
element.style.left = "100px";
// 推荐的方式(只触发重绘)
element.style.transform = "translate(100px, 50px)";
- 缓存布局信息:如果需要多次读取元素的布局属性(如宽度、高度),最好先将这些属性值缓存起来,以免重复触发回流。
// 不推荐的方式(重复触发回流)
var width = element.offsetWidth;
console.log(width);
var height = element.offsetHeight;
console.log(height);
// 推荐的方式(缓存布局属性值)
var width = element.offsetWidth;
var height = element.offsetHeight;
console.log(width, height);
- 使用分离的 DOM 批处理:如果需要多次对 DOM 进行修改,可以将这些修改操作集中在一起,然后再将它们插入到文档中。这样可以减少回流和重绘的次数。
// 不推荐的方式(多次回流和重绘)
for (var i = 0; i < 100; i++) {
var newElement = document.createElement("div");
parentElement.appendChild(newElement);
}
// 推荐的方式(批量插入元素,减少回流和重绘)
var fragment = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
var newElement = document.createElement("div");
fragment.appendChild(newElement);
}
parentElement.appendChild(fragment);
通过采用上述方法,可以最大程度地减少回流和重绘的次数,提高页面的性能和响应速度,提高页面性能和用户体验。