重排(回流)、重绘与合成
前导知识
网页生成过程
- HTML被HTML解析器解析成DOM 树,HTML中的每个tag都是DOM树中的1个节点,根节点就是常用的document对象DOM树里包含了所有HTML标签,包括display:none隐藏,还有用JS动态添加的元素等。
- CSS被CSS解析器解析成CSSOM 树
- 结合DOM树和CSSOM树,生成一棵渲染树(Render Tree),渲染树中每个节点都有自己的样式,而且渲染树不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,所以就不会包含到 render tree中。
- 生成布局(flow),将渲染树的所有节点进行平面合成
- 将布局绘制(paint)在屏幕上
第四步和第五步是最耗时的部分,这两步合起来,就是通常所说的渲染。
渲染
网页生成的时候,至少会渲染一次。
在用户访问的过程中,还会不断重新渲染
重新渲染需要重复之前的第四步(重新生成布局)+第五步(重新绘制)或者只有第五个步(重新绘制)。
重排与重绘的关系
- 重绘:某些元素的外观被改变,例如:元素的填充颜色
- 重排:重新生成布局,重新排列元素。
就如上面的概念一样,单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。
比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
也就是说:"重绘"不一定会出现"重排","重排"必然会出现"重绘"
注意:
- 由于浏览器使用流式布局,对
Render Tree
的计算通常只需要遍历一次就可以完成,但<table/>
及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用<table/>
布局的原因之一。
回流必将引起重绘,重绘不一定会引起回流。
重排/回流 (Reflow)
当DOM的变化(大小、结构、或某些属性)影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
重排也叫回流,重排的过程以下面这种理解方式更清晰一些:
回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流
会导致回流的操作:
页面首次渲染
浏览器窗口大小发生改变——resize事件发生时
添加或者删除可见的DOM元素;
元素内容变化(用户在input框中输入文字或图片大小等等)
元素字体大小变化
添加或者删除可见的
DOM
元素激活
CSS
伪类(例如::hover
)查询某些属性或调用某些方法
常见引起重排属性和方法 width height margin padding display border position overflow clientWidth clientHeight clientTop clientLeft offsetWidth offsetHeight offsetTop offsetLeft scrollWidth scrollHeight scrollTop scrollLeft scrollIntoView() scrollTo() getComputedStyle() getBoundingClientRect() scrollIntoViewIfNeeded()
重排影响的范围
由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:
全局范围:从根节点
html
开始对整个渲染树进行重新布局。有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生重排。
局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。
重绘 (Repaint)
当页面中元素样式的改变并不影响它在文档流中的位置时,浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
常见的引起重绘的属性:
color | border-style | visibility | background |
text-decoration | background-image | background-position | background-repeat |
outline-color | outline | outline-style | border-radius |
outline-width | box-shadow | background-size |
合成(Composite)
更改一个既不要布局也不要绘制的属性,浏览器会跳过布局和绘制,浏览器会主动将渲染层提至合成层,只执行后续的合成操作,以下是影响 composite 的因素:
- 3D transforms: translate3d, translateZ 等;
- video, canvas, iframe 等元素;
- 通过 Element.animate() 实现的 opacity 动画转换;
- 通过 СSS 动画实现的 opacity 动画转换;
- position: fixed;
- will-change;
- filter;
提升为合成层简单说来有以下几点好处:
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
浏览器的渲染队列
div.style.left = '10px'; div.style.top = '10px'; div.style.width = '20px'; div.style.height = '20px';
这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性,实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制:
当修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。
div.style.left = '10px'; console.log(div.offsetLeft);
当在console中访问节点会导致重排的属性时,浏览器会立即重排+重绘,因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘。
强制刷新队列的style样式请求:
clientWidth
、clientHeight
、clientTop
、clientLeft
offsetWidth
、offsetHeight
、offsetTop
、offsetLeft
scrollWidth
、scrollHeight
、scrollTop
、scrollLeft
getComputedStyle()
getBoundingClientRect()
在开发中,应该谨慎的使用这些style请求,注意上下文关系,避免一行代码一个重排,这对性能是个巨大的消耗
性能影响
重排比重绘的代价要更高。
尽可能的减少重排的次数、重排范围
- 重排需要更新渲染树,性能花销非常大:
- 它们的代价是高昂的,会破坏用户体验,并且让UI展示非常迟缓,需要尽可能的减少触发重排的次数。
- 重排的性能花销跟渲染树有多少节点需要重新构建有关系:
- 所以应该尽量以局部布局的形式组织
html
结构,尽可能小的影响重排的范围。而不是一溜的堆砌标签,随便一个元素触发重排都会导致全局范围的重排。
优化措施
CSS
避免使用
table
布局。尽可能在
DOM
树的最末端改变class
。避免设置多层内联样式。
将动画效果应用到
position
属性为absolute
或fixed
的元素上。避免使用
CSS
表达式(例如:calc()
)。优化动画,牺牲一些平滑,来换取速度,比如实现一个动画,以1个像素为单位移动这样最平滑,但是reflow就会过于频繁,大量消耗CPU资源,如果以3个像素为单位移动则会好很多
启用GPU加速,GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。
/* * 根据上面的结论 * 将 2d transform 换成 3d * 就可以强制开启 GPU 加速 * 提高动画性能 */ div { transform: translate3d(10px, 10px, 0); }
JavaScript
分离读写操作/样式集中改变,避免频繁操作样式,或者将样式列表定义为
class
并一次性更改class
属性。div.style.left = '10px'; div.style.top = '10px'; div.style.width = '20px'; div.style.height = '20px'; console.log(div.offsetLeft); console.log(div.offsetTop); console.log(div.offsetWidth); console.log(div.offsetHeight);
避免频繁操作
DOM
,创建一个documentFragment
,在它上面应用所有DOM操作
,最后再把它添加到文档中。也可以先为元素设置
display: none
,操作结束后再把它显示出来。因为在display
属性为none
的元素上进行的DOM
操作不会引发回流和重绘。避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。