前端工程师应该都听说过硬件加速,通常它是指利用 GPU 来加速页面的渲染。那么 GPU 目前在web页面的渲染过程中起到什么作用呢?

GPU 的作用

早期浏览器完全依赖 CPU 来进行页面渲染。现在随着 GPU 的能力增强和普及,且目前绝大多数运行浏览器的设备上都集成了 GPU。浏览器可以利用 GPU 来加速网页渲染。

GPU 包含几百上千个核心,但每个核心的结构都相对简单, GPU 的结构也决定了它适合用来进行大规模并行计算。进行图层合并需要操作大量的像素,这方面 GPU 能比 CPU 更高效的完成。这里有个视频,很清楚地说明 CPU 与 GPU 的差别。

页面渲染过程

浏览器利用 HTML 构建出 DOM 树,利用 CSS 构建 CSSOM 树,最终得到 Render 树。

text=渲染树的构建过程

然而这只是很宏观的描述,浏览器为了将 DOM 元素高效地绘制且正确地出来,将多个元素安排在一个图层中,使用 PaintLayer 来描述,在每个 PaintLayer 中又存在 GraphicsLayers。当某个元素的样式改变后,不需要去重绘某个图层就好了。

浏览器的每一帧都可能会经过以下几个步骤:

JavaScript 的执行可能修改 DOM 树和 CSSOM 树,随后浏览器需要重新计算样式,并根据新的样式计算出元素的实际属性(比如 CSS 中 width 是 50%,这里就要利用父元素的宽度得出自己真实的 width 值),重绘有变动的图层,随后将各图层传递给 GPU ,由 GPU 来进行图层的合并。

上面 5 个步骤中,Layout 和 Paint 是可以省略的,当修改后的样式不会改变元素的尺寸、位置等涉及布局的属性时候,就没有必要进行 Layout(计算布局),比如修改了 color 属性,这个时候就只需要进行重绘(Paint)步骤。同样的道理,修改某些属性也不需要进行 Paint 步骤,只需要 Composite 就可以。

因此,我们希望所做的操作能尽可能地避免 Layout 和 Paint 这两个步骤,这样一帧所需的时间也就会大大缩短,可以明显避免卡顿。

目前有三个属性的改变只需要进行 Composite 过程,分别是:

  • filter
  • transform
  • opacity

这几个属性的改变,GPU 只需要在合并图层之前对图层进行一些变换,比如 opacity 属性的改变,GPU 只需要在合并之前改变图层的 alpha 通道。transform 和 filter 的改变 GPU 也可以很快地得到相应的图层。

正确地利用 GPU

使用 transform, filter 和 opacity 来完成动画

使用以上 3 个属性来完成动画,可以避免在动画的每一帧进行重绘。如果在动画中改变了其他属性,那也不能避免重新绘制。

避免不合理地强制开启硬件加速

常常看到有文章指出使用 transform:translateZ(0); 这样的 hark 可以强制开启硬件加速来提高性能,这是错误的说法,要知道所谓的硬件加速就是利用 GPU 来将本就存在于 GPU 中的图层进行一些变换得到新的图层。如果改变的属性必须要要进行重绘,比如改变了 background 属性,那么图层还是要进行重绘然后重新加载至 GPU 中。这个时候就算强制开启硬件加速也没有什么用。

使用 transform:translateZ(0); 这样的 CSS hark 写法会将元素提升至单独的图层。在这么做之前要考虑为什么要这样做,创建新的图层的目的应该是,避免某个元素的改变导致大面积重绘,比如某个小标签的颜色的改变,导致大面积重绘,因此将其提升至单独的图层中。这里有个例子,小标签背景色的改变会导致大面积的重绘,但是如果将其提升至单独的图层后,改变它的背景色将只会重绘它自身。你可以代码 Chrome 调试工具,通过 Timeline 观察每次闪烁重绘的内容。

而如果整个图层的都要被重绘,那么再将其中的部分元素提升至单独的图层,会导致重绘的时候会分多个图层来进行绘制,然后在进行多个图层的合并,这个时候不如将所有元素放置在单个图层中,重绘整个大的图层。

总结

所谓硬件加速,早起浏览器是使用纯软件来渲染页面的,如今现代浏览器利用了 GPU 来进行页面的渲染,在合适的时候浏览器就会自动去使用 GPU 而不是开发者自己去指定。GPU 的功能是在合并图层阶段,它可以在进行图层合并之前来对原图层进行一些变换,合理地使用这个变换可以避免页面重绘,使得每一帧消耗的时间最少,避免卡顿。