文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是 把这些信息转化为显示器中的像素,这个转化的过程,叫做 光栅化(rasterizing)。
那我们要绘制一个页面,最简单的做法是只光栅化视口内(viewport)的网页内容,如果用户进行了页面滚动,就移动光栅帧(rastered frame)并且光栅化更多的内容以补上页面缺失的部分,如下:

Chrome 第一个版本就是采用这种简单的绘制方式,这一方式唯一的缺点就是每当页面滚动,光栅线程都需要对新移进视图的内容进行光栅化,这是一定的性能损耗,为了优化这种情况,Chrome 采取一种更加复杂的叫做合成(compositing)的做法。
那么,什么是合成?
合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程(compositor thread)里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。

为了实现合成技术,我们需要对元素进行分层,确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree),对于添加了 will-change CSS 属性的元素,会被看做单独的一层,没有 will-change CSS 属性的元素,浏览器会根据情况决定是否要把该元素放在单独的层。

你可能会想要给页面上所有的元素一个单独的层,然而当页面的层超过一定的数量后,层的合成操作要比在每个帧中光栅化页面的一小部分还要慢,因此衡量你应用的渲染性能是十分重要的一件事情。
一旦 Layer Tree 被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次数的每一层进行光栅化。有的层的可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块(Tiles),之后将这些小图块分别进行发送给一系列光栅线程(raster threads)进行光栅化,结束后光栅线程会将每个图块的光栅结果存在 GPU Process 的内存中。

为了优化显示体验,合成线程可以给不同的光栅线程赋予不同的优先级,将那些在视口中的或者视口附近的层先被光栅化。
当图层上面的图块都被栅格化后,合成线程会收集图块上面叫做绘画四边形(draw quads)的信息来构建一个合成帧(compositor frame)。
以上所有步骤完成后,合成线程就会通过 IPC 向浏览器进程(browser process)提交(commit)一个渲染帧。这个时候可能有另外一个合成帧被浏览器进程的 UI 线程(UI thread)提交以改变浏览器的 UI。这些合成帧都会被发送给 GPU 从而展示在屏幕上。如果合成线程收到页面滚动的事件,合成线程会构建另外一个合成帧发送给 GPU 来更新页面。

合成的好处在于这个过程没有涉及到主线程,所以合成线程不需要等待样式的计算以及 JavaScript 完成执行。这就是为什么合成器相关的动画最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。
浏览器接收到网站页面文档后,会将文档中的标记语言解析为 DOM 树。DOM 树和 CSS 结合形成浏览器构建页面的渲染树。渲染树中包含了大量的渲染元素,每个渲染元素会被分到单独一个图层中,每个图层又会被加载到 GPU 形成渲染纹理,而图层在 GPU 中 transform 是不会触发 repaint 的,这点非常类似 3D 绘图功能,最终这些使用 transform 的图层都会由 独立的合成器进程进行处理。
CSS transform 创建新的复合图层,可以被 GPU 直接用来执行 transofmr 操作。在 Chrome 开发者工具中开启 Show Layer Border 选项后,每个复合涂层就会显示一条黄色的边界。
每个页面元素都有一个独立的渲染进程,包含了主线程和合成线程。
当我们通过某种方法引起浏览器的 reflow 时,需要重新经历样式计算(Style Calculation)和布局(Layout)阶段,导致浏览器重新计算页面中每个 DOM 元素的尺寸及重新布局,伴随着重新进行 repaint,这个过程是非常耗时的。为了把代价降到最低,当然最好只留下合成(Composite)这个步骤最好。假设当我们改变一个容器的样式时,影响的只是它自己,并且还无需重绘,直接通过 GPU 中改变纹理的属性来改变样式,岂不是更好?
如果能让元素达到这个效果?
就是让 DOM 元素拥有自己的层(Layer)。有了层的概念,让我们从层的概念再来看浏览器的渲染过程:
可以将这个过程理解为设计师的 Phtoshop 文件。在 PS 源文件中,一个图像是由若干个图层相互叠加而展示出来的。分成多个图层的好处就是每个图层相对独立,修改方便,对单个图层的修改不会影响到页面上的其他图层。因此层(Layer)存在的意义在于:用最小的代价来改变某个页面元素。可以将某个 CSS 动画或某个 JS 交互效果抽离到一个单独的渲染层,来达到加速渲染的目的。
那么哪些规则能让浏览器主动帮我们创建独立的层呢?
perspective 和 transform) 的 CSS 属性backgace-visibility 为 hidden 的元素<video> 元素<canvas> 元素opacity、transform、filter、backdrop-filter 应用了 animation 或者 transition(需要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,合成层也会失效)will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等需要设置明确的定位属性,如 relative 等)filter 过滤器的元素z-index 较小,那这个元素也会被应用到复合图层实际优化点:
translate 替代 top 改变opacity 替代 visibilityclass,然后修改 DOM 的 classNamedisplay: none(有一次 Reflow 重排),然后你修改 100 次,然后再把它显示出来table 布局,可能很小的一个小改动会造成整个 table 的重新布局CSS 中以下几个属性能触发硬件加速:
transformopacityfilterwill-change如果有一些元素不需要用到上述属性,但是需要触发硬件加速效果,可以使用硬编码的技巧来诱导浏览器开启硬件加速。
.element {-webkit-transform: translateZ(0);-moz-transform: translateZ(0);-ms-transoform: translateZ(0);-o-transoform: translateZ(0);transform: translateZ(0);transform: rotateZ(360deg);transform: translate3d(0, 0, 0);}
这段代码的作用就是让浏览器执行 3D transform。浏览器通过该样式创建了一个独立图层,图层中的动画则有 GPU 进行预处理并且触发了硬件加速。
如果某个元素的背后是一个复杂元素,那么该元素的 repaint 操作就会耗费大量的资源,此时也可以使用上面的技巧来减少性能开销。
注意事项: