本文以谷歌浏览器的渲染流程展开讲解,以下是谷歌浏览器完整的渲染流程图,具体过程不过多解释,有兴趣看下《页面的渲染流程(Chrome)》
图片来源 The Anatomy of a Frame
重排(reflow) |
重排又称重构、回流,当我们通过JavaScript或者CSS修改了元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段。
可见,重排需要更新完整的渲染流水线,所以开销也是最大的。每个页面至少需要一次reflow,就是在页面第一次加载的时候。
触发重排条件 |
任何页面布局和几何属性的改变都会触发重排,比如:
- 页面渲染初始化;(无法避免)
- 添加或删除可见的DOM元素;
- 元素位置的改变;
- 改变元素尺寸(宽、高、内外边距、边框等);
- 浏览器窗口尺寸的变化(resize事件发生时);
- 填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变;
- 读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )
重绘(repaint) |
当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。
重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
比如通过 JavaScript 更改某些元素的背景颜色,没有改变元素的几何属性,那么布局阶段不会执行,而是直接进入绘制阶段。
相比于重排,重绘省去了布局和分层阶段,效率会高于重排。重排必定会引发重绘,但重绘不一定会引发重排。
触发重绘条件 |
改变元素外观属性。如:color,background-color
等。
合成(composite) |
更改一个既不要布局也不要绘制的属性,如使用了 CSS 的 will-change: transform
来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作,这个过程叫做合成。
这就是所谓的硬件加速!将使用了will-change: transform
属性的元素单独提升为一层,避免动画位置发生改变的时候的就需要不停的重排,如果渲染速度不够快,还会掉帧,动画显得卡顿。但是将其提升为独立一层,直接交给GPU处理,无论怎么变化,不会影响原有图层的GraphicsContext
。
具体有哪些css属性会引起硬件加速呢?怎么看其重绘的过程呢?👉浏览器渲染图层VS复合图层(硬件加速)
毫无疑问,这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。但亦非可以无限制的使用,超过一定量复合图层也会导致GPU 动画性能下降。
浏览器实际上是如何处理重绘与回流的? |
如果每次触发回流重绘操作都进行页面的回流重绘的话,浏览器的负担就太重了。
实际上,浏览器会维护一个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush
队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。
虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush
队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些 style
信息(实时计算出来的精确信息)的时候,就会让浏览器flush
队列,如:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
width,height
- 请求了
getComputedStyle()
, 或者 IE的currentStyle
当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush
队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。引擎会重新渲染来确保获取的值 是实时的。
如何减少重排与重绘 |
1.使用 class 操作样式,而不是频繁操作内联style
其实也就是样式集中管理,多个元素使用同一style,都需要改变的话就需要触发多次重绘或重排,使用class一次搞定。
2.避免使用 table 布局
table及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用table布局页面的原因之一。
3.批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React虚拟节点,避免全部元素重排,只是对改变的节点更新
4.Debounce window resize 事件(防抖)
5.对 dom 属性的读写要分离
写:
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);
样式的设置是放入渲染队列中的,要获取样式属性值,会直接将渲染队列flush。像这样读写分离的话,只执行了一次重排。
如若:
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
执行了两次次重排。
6.will-change: transform
做优化 (CSS 硬件加速)
7.将需要多次重排的元素(需要动画表现的元素),position:absolute or fixed;
z-index不为auto或0
,元素脱离了文档流,它的变化不会影响到其他元素,降低重排的代价;
8.先设置元素为display:none;
然后进行页面布局等操作;设置完成后将元素设置为display:block;
这样的话就只引发两次重绘和重排;
9.使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;