回流与重绘
一、浏览器的渲染过程
浏览器在渲染的时候,会解析html生成DOM树,解析css生成CSSOM树,然后两个树合并生成渲染树
生成渲染树浏览器完成了那些工作?
- 从DOM数的根节点开始遍历可见的节点
- 对于可见的节点,找到CSSOM树中对应的规则,并应用它们
- 根据每个节点和它对应的样式,生成渲染树
注意:不可见节点包括script、meta、link等,以及通过css进行隐藏的节点,比如display:none。但visibility和opacity隐藏的节点,还是会显示在渲染树上的。
二、回流
构造完渲染树后,计算它们在设备视口内的确切位置和大小,这个计算阶段就是回流。
下面有个实例:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>回流</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>
第一个div节点将显示尺寸设置为视口宽度的50%,第二个div节点将显示尺寸显示为第一个div宽度的50%,而在回流这个阶段,我们就需要根据视口具体的宽度,将其转换为具体的像素值。
三、重绘
最终,我们通过构造渲染树和回流阶段,我们知道了那些节点是可见的,以及可见节点的样式和具体的几何信息,那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。(说人话就是将每个节点上对应的样式渲染出来)
四、何时发生回流重绘
回流这一阶段是计算节点位置和几何信息,那么页面上的布局发生改变时就会发生回流,比如以下几种状况:
- 添加或删除可见的DOM元素
- 元素的位置发生改变
- 元素的尺寸发生变化(外边距,边框、内边距、宽高等)
- 元素内容发生变化
- 页面打开渲染时
- 浏览器的窗口尺寸变化
注意:回流一定会触发重绘,但重绘不一定会触发回流
根据改变的范围和程度,渲染树中或大或小的部分需要重新计算,有些改变会触发整个页面的重排,比如滚动条出现的时候或者修改了根节点。
五、浏览器优化机制
现在的浏览器会通过队列化修改并批量执行来优化重排过程,浏览器会将修改操作放到队列里,直到过了一段时间或者操作达到了阈值,才清空队列。当你获取布局信息的操作的时候,会强制更新队列。比如你访问了以下属性或方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyel()
- getBoundingClientRect()
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。
六、如何减少回流和重绘
最小化重绘和重排
合并对DOM的操作,将多次对DOM的操作合并为一次,减少发生次数。
批量修改DOM
当我们需要对DOM进行一系列的修改时,可以通过以下步骤减少回流重绘次数:
- 让元素脱离文档流
- 对其进行多次修改
- 将元素带回到文档中
有三种方式可以让DOM脱离文档流:
- 隐藏元素,应用修改,重新显示
- 使用文档碎片(documentFragment)在当前DOM之外构建一个子树,再把它拷贝回文档中。
- 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。
避免触发同步布局事件
function initP () {
for (let i = 0; i < 10; i++) {
p[i].style.width = box.offsetWidth + 'px'
}
}
上面这段代码在每次循环的时候,都会读取box的offsetWidth属性值,然后更新p标签的width属性。导致每一次循环的时候,浏览器都必须使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。
我们可以这样优化代码:
function initP () {
const boxWidth = box.offsetWidth
for (let i = 0; i < 10; i++) {
p[i].style.width = boxWidth + 'px'
}
}
对于复杂动画效果,使用绝对定位让其脱离文档流
CSS3硬件加速(GPU加速)
比起如何减少回流重绘,我们更期望的是,不要回流重绘。这个时候,CSS3硬件加速就闪亮登场啦。
使用CSS3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘。但是对于动画的起他属性,比如background-color这些,还是会引起回流重绘的。