前言:回流(重排)和重绘是前端面试过程中经常会提到的一个知识点,所以想写一篇文章整理下相关的知识点。
要了解什么是回流(重排)与重绘,首先就要了解一下流浪其是如何进行画面渲染的。
浏览器是如何进行画面渲染的?
- 解析(Parser)HTML,生成DOM树(Tree),解析(Parser)CSS,生成样式规则(Style Rules)
- 根据刚才生成的DOM树与样式规则,生成渲染树(Render Tree)
- 进行布局Layout(回流/重排):根据生成的渲染树,得到节点的集合信息(位置,大小)
- 进行绘制Painting(重绘):根据计算和获取的信息进行整个页面的绘制
- Display:最后将画面显示在页面上
重绘和回流(重排)
- 回流:渲染树(Render Tree)中的元素的尺寸、结构、布局(几何属性)发生改变的时候,浏览器就会重新渲染部分或者全部文档的过程。
- 重绘:对元素样式的改变并不影响它在文档流中的位置和文档布局(没有改变元素的几何属性),浏览器直接为该元素绘制新的样式。
回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
如何触发
回流(重排)
-
页面一开始加载的时候(无法避免)
-
脚本操作DOM(增加或者删除可见的DOM元素)
-
元素的几何属性发生变化(大小,位置)
-
元素的内容发生了变化(如:input框中输入的内容,图片被另一个不同尺寸的图片所替代)
-
激活css伪类(如:
#div::hover
) -
字体的大小发生改变
-
浏览器的窗口大小发生了改变(回流是根据视口的大小来计算元素的位置和大小的)
-
使用一些特定的属性
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
这些属性是通过实时计算得到的,浏览器获取这些值的时候,也会进行回流的操作,使用
getComputedStyle
方法的时候同理
大部分时候可以认为,只要影响到页面布局就会有回流的发生。
重绘
- 上文提到的回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
- 颜色的修改
- 文本方向的修改
- 阴影的修改
下面来看几个和重绘与回流相关的案例熟悉一下。
案例1:
|
|
案例2:
问题:display:none
和visibility:hidden
会产生回流与重绘吗?
答:
-
display:none
元素隐藏之后不占用文档流(在渲染树里面不存在节点),DOM树发生了变化,所以会引起重绘与回流。 -
visibility:hidden
显示在页面上,但是隐藏元素仍需占用与未隐藏时一样的空间(在渲染树里面存在节点),没有影响到页面的结构,所以只有产生重绘。
补充:display: none
的子元素不会进行显示,而visibility: hidden
的子元素却是可以进行设置显示的
浏览器优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。
浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列.
当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop
等方法都会返回最新的数据
因此浏览器不得不清空队列,触发回流重绘来返回正确的值。
如何避免触发回流与重绘
- 如果想通过js修改样式最好通过类(
class
)的方式去触发(补充:使用cssText也可以) - 避免设置多项内联样式
- 批量修改dom时候通过以下思路减少回流与重绘的发生
- 使元素脱离文档流
- 对其进行多次修改
- 将元素带回到文档中
- 避免触发同步布局事件,比如前文提到的需要实时读取的属性(如:
offsetWidth
),这样不需要每次循环的时候都读取一次 - 遇到复杂的动画效果,使用
position: fixed/absolute
让其脱离文档流,从而减少对其他元素的影响 - css3硬件加速,可以让
transform
、opacity
、filters
这些动画不会引起回流重绘。- 如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
- 在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。
设定元素样式,最好通过类(class
)的方式去触发
下面这种方式,在比较老的浏览器上每次赋值操作都会引起回流与重绘
❕注:比较新的浏览器会使用队列来储存多次修改,进行优化,所以在新的浏览器上只会触发一次重绘和回流
|
|
为了避免触发重回与回流,可以使用类(class
)来给元素触发对应的样式,下面就是优化过后的代码:
CSS:
|
|
JS:
|
|
补充:其实还可以使用cssText
来给样式做重新赋值,但是这种方式写法看上去比较繁琐,所以了解一下就好了。对应的代码如下:
|
|
批量修改dom时候通过以下思路减少回流与重绘的发生
- 使元素脱离文档流
- 对其进行多次修改
- 将元素带回到文档中
该过程的第一步和第三步可能会引起回流,但是经过第一步之后,对DOM的所有修改都不会引起回流,因为它已经不在渲染树了。
有三种方式可以让DOM脱离文档流:
- 隐藏元素,应用修改,重新显示
- 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
- 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。
隐藏元素,应用修改,重新显示
|
|
将ul
的display
设置为none
后,该元素就不存在与渲染树中了
使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档
|
|
DocumentFragments (en-US) 是 DOM 节点。它们不是主 DOM 树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。——(MDN-Document.createDocumentFragment())
将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。
|
|
避免触发同步布局事件
|
|
css3硬件加速(GPU加速)
划重点:使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。
常见的触发硬件加速的css属性:
- transform
- opacity
- filters
- Will-change
注意:
- 如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。
- 在GPU渲染字体会导致抗锯齿无效。这是因为GPU和CPU的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。