渲染流程大致可总结为如下:
-
渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
-
渲染进程将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
-
创建布局树,并计算元素的布局信息。
-
对布局树进行分层,并生成分层树。
5.为每个图层生成绘制列表,并将其提交到合成线程。
-
合成线程将图层分为图块,并在光栅化线程池中将图块转换成位图。
-
合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
-
浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上
其中有三个和渲染流水线相关的概念—— “重排” “重绘” 和 “合成”。
回流/重排(Reflow)
从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重现布局,解析之后的一系列子阶段,这个过程就叫做重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。
哪些操作会导致回流?
以下会导致回流的操作:
页面首次渲染
浏览器窗口大小发生改变
元素尺寸或位置发生改变(如边距、填充、边框、宽度和高度、position、left、top等)
元素内容变化(文字数量或图片大小等)
改变元素字体大小
设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow
dom操作(添加或删除可见的DOM元素等)
css伪类激活
查询某些属性或调用某些方法(如计算元素的offsetWidth、offsetHeight等)
常用且会导致回流的属性和方法:
width、height、margin、padding、border、border-width
display、position、overflow、font-size、vertical-align、min-height
clientWidth/Height/Top/Left
offsetWidth/Height/Top/Left
scrollWidth/Height/Top/Left
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()、currentStyle(IE获取样式)
getBoundingClientRect()
scrollTo()\
重绘 (Repaint)
从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫做重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
简单地说,重绘是填充像素的过程。它涉及绘出文本、颜色、图像、边框 和阴影,基本上包括元素的每个可视部分。在重绘阶段,系统会遍历调用渲染对象的 paint 方法,将渲染对象的内容显示在屏幕上。
注意:由页面的渲染过程可知,reflow必将会引起repaint,而repaint不一定会引起reflow。
哪些操作会导致重绘?
color、border-style、visibility、text-decoration
background、outline、box-shadow等
合成
那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成
在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没与占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。这就是所谓的GPU加速。
有两大好处:
能够充分发挥GPU的优势。合成线程生成位图的过程中会调用线程池,并在其中使用GPU进行加速生成,而GPU 是擅长处理位图数据的。
没有占用主线程的资源,即使主线程卡住了,效果依然能够流畅地展示。
避免与优化
CSS
- 避免使用
table布局。 - 尽可能在
DOM树的最末端改变class。 - 避免设置多层内联样式。
- 将动画效果应用到
position属性为absolute或fixed的元素上。 - 避免使用
CSS表达式(例如:calc())。
JavaScript
- 避免频繁操作样式,最好一次性重写
style属性,或者将样式列表定义为class并一次性更改class属性。 - 离线dom:
- 避免频繁操作
DOM,创建一个documentFragment,使用DocumentFragment进行缓存操作,引发一次回流和重绘, 在它上面应用所有DOM操作,最后再把它添加到文档中。 - 也可以先为元素设置
display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
- 避免频繁操作
- 缓存布局信息
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 例如:
// 不好的写法
for(let i = 0; i < 20; i++ ) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 可以先缓存起来
var left = el.offsetLeft, top = el.offsetTop, style = el.style;
for (let i = 0; i < 20; i++ ) {
left += 5;
top += 5;
style.left = left + "px";
style.top = top + "px";
}
-
使用 absolute 或 fixed 脱离文档流
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
-
优化动画
-
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
-
启用GPU加速
-
GPU 加速通常包括以下几个部分:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。
/* 根据上面的结论, 将 2d transform 换成 3d,就可以强制开启 GPU 加速,提高动画性能 */
div {
transform: translate3d(10px, 10px, 0);
}