前言
近些年,我们总是听到硬件加速,以及它如何帮助我们提升网页的动画性能,让网页动画变得更好,在移动端更流畅。但是我想一大部分经验少的工程师是不知道硬件加速是如何工作的以及我们如何使用它来帮助我们让动画变得更流畅。
CSS3 硬件加速又叫做 GPU 加速,是利用 GPU 进行渲染,减少 CPU 操作的一种优化方案。由于 GPU 中的 transform3d 等 CSS 属性不会触发 repaint,所以能大大提高网页的性能。
GPU(图像处理器):
GPU 硬件加速是指应用 GPU 的图形性能对浏览器中的一些图形操作交给 GPU 来完成,因为 GPU 是专门为处理图形而设计,所以它在速度和能耗上更有效率
案例
左边元素的动画通过 left/top 操作位置实现,右边元素的动画通过 transform: translate
实现,你可以打开 chrome 的 “Paint flashing” 查看,绿色部分是正在 repaint 的内容。
查看地址
从 demo 中可以看到左边的图形在运动时外层有一圈绿色的边框,表示元素不停地 repaint,并且可以看到其运动过程中有丢帧现象,具体表现为运动不连贯,有轻微闪动。
那么transform
是如何让动画不会导致重绘的呢?最直接的答案就是transform
会直接使用硬件加速,在GPU
中运行,绕开了软件渲染。
硬件加速如何工作的
之前学习 flash 的时候,就知道动画是由一帧一帧的图片组成,在浏览器中也是如此。我们首先看一下,浏览器每一帧都做了什么。
- JavaScript:JavaScript 实现动画效果,DOM 元素操作等。
- Style(计算样式):确定每个 DOM 元素应该应用什么 CSS 规则。
- Layout(布局):计算每个 DOM 元素在最终屏幕上显示的大小和位置。由于 web 页面的元素布局是相对的,所以其中任意一个元素的位置发生变化,都会联动的引起其他元素发生变化,这个过程叫 reflow。
- Paint(绘制):在多个层上绘制 DOM 元素的的文字、颜色、图像、边框和阴影等。
- Composite(渲染层合并):按照合理的顺序合并图层然后显示到屏幕上。
动画与图层
浏览器在获取 render tree(详细知识可以查看深入了解浏览器重排和重绘)后,渲染树中包含了大量的渲染元素,每一个渲染元素会被分到一个图层中,每个图层又会被加载到 GPU 形成渲染纹理。这里的秘诀就在于通过transform
的层会使用GPU
渲染,因此不需要重绘,这一点非常类似 3D 绘图功能,最终这些使用 transform 的图层都会由独立的合成器进程进行处理。
过程如下:
render tree -> 渲染元素 -> 图层 -> GPU 渲染 -> 浏览器复合图层 -> 生成最终的屏幕图像。
注意:
chrome devtools 中可以开启 Rendering 中的 Layer borders 查看图层纹理。
其中黄色边框表示该元素有 3d 变换,表示放到一个新的复合层(composited layer)中渲染,蓝色栅格表示正常的 render layer。
在文章开始给出的例子中,CSS
的transform
在GPU
直接创建一个新的层。我们也可以开启 Layer borders,(这个选项可以帮助我们查看哪些是单独的层,开启这个选项以后单独的层会具有一个橙色的边框。)可以观察到,使用 transform: translate
动画的元素,外围有一个黄色的边框,可知其为复合层。
在 GPU 渲染的过程中,一些元素会因为符合了某些规则,而被提升为独立的层(黄色边框部分),一旦独立出来,就不会影响其它 DOM 的布局,所以我们可以利用这些规则,将经常变换的 DOM 主动提升到独立的层,那么在浏览器的一帧运行中,就可以减少 Layout 和 Paint 的时间了。
创建独立图层
哪些规则能让浏览器主动帮我们创建独立的层呢?
- 3D 或者透视变换(perspective,transform) 的 CSS 属性。
- 使用加速视频解码的 video 元素。
- 拥有 3D(WebGL) 上下文或者加速 2D 上下文的 canvas 元素。
- 混合插件(Flash)。
- 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素。
- 拥有加速 CSS 过滤器的元素。
- 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)。
- 元素有一个兄弟元素在复合图层渲染,并且该兄弟元素的 z-index 较小,那这个元素也会被应用到复合图层。
开启 GPU 加速
CSS的animation、tranform、transition并不会自动开启GPU加速,而是通过浏览器的缓慢的软件渲染引擎来实现执行,那么我们怎么才能实现GPU加速呢,很多浏览器提供了某些触发该模式的规则。
比如使用 translate3d() rotate3d() scale3d() 这几个方法,我们就可以使用GPU加速了。
如下几个css属性可以触发硬件加速:
- transform( translate3d、translateZ(0)等)
- opacity
- filter(滤镜:drop-shadow()、opacity(),函数与已有的
box-shadow、opacity
属性很相似;不同之处在于,通过滤镜,一些浏览器为了更好的性能会提供硬件加速) - will-change:哪一个属性即将发生变化,进而进行优化。
因此为了页面更加流畅,高性能的动画,我们可以使用GPU
来处理。
如果有一些元素不需要用到上述属性,但是需要触发硬件加速效果,例如:某些情况下,我们并不想要对元素应用3D变换的效果,却还想要实现GPU加速,可以使用一些小技巧来诱导浏览器开启硬件加速。
transform: translateZ(0)
这个声明就是可以触发桌面端和移动端的GPU加速,这是一个非常有效的方式(包含所有的浏览器前缀):
.element {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
/**或者**/
transform: rotateZ(360deg);
transform: translate3d(0, 0, 0);
}
使用硬件加速需要注意的地方
Memory
大部分重要的问题都是关于内存。GPU
处理过多的内容会导致内存问题。这在移动端和移动端浏览器会导致崩溃。因此,通常不会对所有的元素使用硬件加速。(过多地开启硬件加速可能会耗费较多的内存,因此什么时候开启硬件加速,给多少元素开启硬件加速,需要用测试结果说话。)
Font rendering
在GPU
渲染字体会导致抗锯齿无效。这是因为GPU
和CPU
的算法不同。因此如果你不在动画结束的时候关闭硬件加速,会产生字体模糊。
The Near Future
有必要使用transform hack
的地方是提高性能。浏览器自身也提供了优化的功能,这也就是will-change
属性。这个功能允许你告诉浏览器这个属性会发生变化,因此浏览器会在开始之前对其进行优化。这里有一个例子:
.example {
will-change: transform;
}
遗憾的是,并不是所有浏览器都支持这个功能。
GPU优点
GPU
渲染可以提高动画性能GPU
渲染会提高动画的渲染帧数
参考
Web 性能优化-CSS3 硬件加速(GPU 加速)(推荐)
在 CSS 动画中使用硬件加速(翻译)(结合推荐)
https://www.shuzhiduo.com/R/KE5Q009kJL/ (很多文章关于硬件加速)
https://www.nowcoder.com/questionTerminal/0260851028b740a1a58370495fe1077d(牛客面试题)