重绘(Repaint)和回流(Reflow)
什么是回流
通过构造render tree,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建时,这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。
在回流的时候,浏览器会从html
这个root frame
开始递归往下,依次计算所有的结点几何尺寸和位置,以确认是渲染树的一部分发生变化还是整个渲染树。然后使渲染树中受到影响的部分失效,并重新构造这部分的渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称之为重绘。
什么是重绘
当render tree中的一些元素需要更新属性时,而这些属性只是影响元素的外观、风格而不会影响布局的时候,比如background-color,文字颜色、边框颜色等。则就称为重绘。
需要注意的是,
display:none
会触发reflow
,而visibility: hidden
属性则并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框。所以visibility:hidden
只会触发repaint
,因为没有发生位置变化。区别
回流必将引起重绘,重绘不一定引发回流,回流所需的成本比重绘高很多,改变父节点里的子节点很可能会导致父节点的一系列回流。
何时发生回流重绘
- 页面第一次渲染(初始化)
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距,内边距,边框大小,高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代或者字体改变
- 浏览器窗口尺寸的变化(因为回流是根据视口的大小来计算元素的位置和大小的)
- 定位或者浮动,盒模型等
- 获取元素的某些属性
浏览器的优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列中,知道过了一段时间或者操作达到了一个阈值,才清空队列。进行一个批处理,这样就会让多次的回流、重绘变成一次回流重绘。但是当获取布局信息的操作的时候,会强制队列刷新,比如访问以下属性或者使用以下方法:
- offsetTop, offsetLeft, offsetWidth, offsetHeight(位置:相对于已定位祖先元素的位置,屏幕尺寸:包含元素的边框和内边距,除去外边距)
- scrollTop, scrollLeft, scrollWidth, scrollHeight(位置:指定元素的滚动条的位置,尺寸:元素的内容区域加上它的内边距再加上任何溢出内容的尺寸)
- clientTop, clientLeft, clientWidth, clientHeight(位置:返回元素的内边距的外边缘和它的边框的外边缘之间的水平距离和垂直距离,通常这些值就等于左边和上边的边框宽度。尺寸:不包含边框大小,只包含内容和它的内边距,也不包含滚动条)
- getComputedStyle()
- getBoundingClientRect()
因此我们在修改样式的时候,最好避免使用上面列出的属性,它们都会刷新渲染队列。如果要使用,最好将值缓存起来。
减少重绘和回流
- 使用transform做形变和位移替代定位top
- 使用visibility替换display:none, 因为前者只会引起重绘,而后者会引起回流
- 不要使用table布局,可能很小的一个改动会造成整个table的重新布局
- 动画实现的速度选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
- CSS选择符会从右往左匹配查找,因此要避免层级过多
- 将频繁重绘或回流的节点设置为图层,图层能够阻止该节点渲染行为影响别的节点。比如video标签,浏览器会自动将该节点变为图层
- 避免多次读取某些属性
- 合并多次对DOM和样式的修改,然后一次处理掉
// 以下三个样式属性都被修改了,每个都影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。 const el = document.getElementById('test'); el.style.padding = '5px'; el.style.borderLeft = '1px'; el.style.borderRight = '2px'; // 合并所有的改变然后一次处理 const el = document.getElementById('test'); el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
- 当我们需要对DOM做一系列操作时,可以通过以下步骤减少回流重绘次数
- 使元素脱离文档流(隐藏元素/使用文档片段(fragment)在当前DOM之外构建一个子树,再把它拷贝回文档/将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素)
- 对其进行多次修改
- 将元素带回到文档中
- 对于复杂动画效果,可以使用绝对定位使其脱离文档流。否则会引起父元素及后续元素的频繁回流
- CSS3硬件加速(GPU加速):使用CSS3硬件加速,可以让transform,opacity,filters这些动画不会引起回流重绘。但是对于动画的其他属性,如background-color,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。但是如果为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题。