CSS动画的性能分析和浏览器GPU加速
此文已由作者袁申授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
有数的数据大屏可以在一块屏幕上展示若干张不同的图表,以炫酷的方式展示各种业务数据。其中有些图表使用CSS实现了饼图轮播、地图标记点闪烁等动画,然而在一张大屏上同时显示了许多张图表时,持续的动画效果有时会出现掉帧、卡顿的情况,需要对动画性能进行优化。本文简单介绍了chrome浏览器性能分析工具和CSS动画使用GPU加速进行性能优化的解决方案。
浏览器渲染流程
这是浏览器渲染引擎的处理过程:
接收到文档后,渲染引擎会对HTML文档进行解析生成DOM树、对CSS文件进行解析生成CSSOM树;同时执行页面中的JavaScript代码;最终根据DOM树和CSSOM树,计算样式(Caluclate Style)生成渲染树,渲染树中,只会包含即将显示在页面中的元素及其样式信息(如head元素、display为hidden的元素就不会包含在渲染树中);根据渲染树需要进行布局(layout)来计算每个元素在页面上的位置;
接下来渲染引擎开始进行绘制(paint),这一步分为若干阶段:根据渲染树将每层(layer)的各个元素绘制,然后将绘制出的若干连续图像进行栅格化(Rasterization),最后将栅格化后的图像合成(composite)为要显示在屏幕上的图像。对每一层的绘制是由浏览器来完成的;最后的合成是由GPU来完成;而栅格化过程取决于浏览器的设置,chrome默认开启GPU栅格化,否则由CPU进行。
当首次将DOM树构建完成后,每次页面发生改变时进行进行的主要流程为:
其中CSS动画不会调用JavaScript,我们知道,在渲染中主要消耗时间的是Layout/Reflow和Paint/Repaint的过程,因此要尽量避免和减少这两个阶段的时间。
影响CSS动画性能的因素
这是一个CSS动画,控制一个方块的top、left属性实现平移。使用Chrome提供的浏览器性能分析工具分析动画的性能,打开浏览器开发者工具后,在标签中选择performance打开性能分析面板。在性能分析面板中对当前页面进行录制,录制结束后分析结果中可以查看页面在这段时间内的FPS、CPU占用等情况,在main中包含了浏览器的渲染流程,包括Scripting、Rendering、Painting等。
执行这个CSS动画时,不涉及JavaScript的调用;紫色部分是render,依次分别为Recaculate Style、Layout和Update Layer Tree;绿色部分为Painting,依次分别为Paint和Composite Layers。
在下面的面板中也可以查看当前时间段内各个阶段执行时间
more tools中的rendering也包含若干查看渲染有关的选项:
勾选paint flashing,页面上会以绿色方块显示需要重绘的区域,当前小方块进行了平移,因此需要重绘;勾选layer borders,会以黄色方框显示页面的分层情况,此时页面只有一个层,蓝色的线显示了tile的划分,它是一个layer中的分块;FPS meter可以显示页面的FPS、是否使用GPU进行栅格化过程和GPU显存使用情况,由于默认开启了GPU栅格化,GPU Raster显示为on:
more tools中的layers可以查看页面分层,以及每层的详细信息等:
通过对这些数据我们可以对页面加载的性能瓶颈进行分析和针对性的优化。
使用GPU加速
浏览器的GPU加速功能是将需要进行动画的元素提升到一个独立的层(layer),这样就可以避免浏览器进行重新布局(Reflow)和绘制(Repaint),将原先的浏览器使用CPU绘制位图来实现的动画效果转为让GPU使用图层合成(composite)来实现,如果两张图层内部没有发生改变,浏览器就不再进行布局和绘制,直接使用GPU的缓存来绘制每个图层,GPU只负责将各个图层合成来实现动画,这就可以充分利用GPU的资源和优势,减轻CPU的负载,可以使动画更流畅。通过改变两张图片之间的相对位置代替绘制一张图片的每一帧来实现动画,虽然视觉效果相同,但省去了许多绘制的时间。
为了让浏览器将动画元素提升到一个独立的层,可以使用transform和opacity属性来实现动画,当设置了这两个属性之一时,浏览器会自动进行这一优化操作(透明度的变化可以通过GPU改变a通道来实现,不需要浏览器进行重绘)。对于上面的动画,可以改变transform来代替改变left和top属性:
对这个动画进行分析,可以看到使用transform后浏览器为小方块单独设置了一个层,并且不再触发浏览器更新样式:
如果动画并不需要对transform和opacity属性做出改变,可以使用其他的方法强制浏览器为这些元素创建单独的层,比如设置一个没有效果的样式:transform:translateZ(0);这不会对元素的实际样式做出改变。但这是一种hack,规范的做法是使用will-change属性,设置它的值为需要做变换的属性,如will-change: left;浏览器就会知道left这个属性会发生变化,因此会开启硬件加速优化性能。
这是使用will-change属性的平移动画,同样也为小方块设置了单独的图层。
避免过度绘制
既然设置了will-change属性可以开启GPU加速,那么:
* { will-change: all;}
看起来好像是一劳永逸的方法,但其实这反而会降低页面的性能,虽然硬件加速可以提高GPU的使用,但从layers中的信息可以看出,每个层都需要消耗一定的内存,过多的内存占用也会造成性能的下降;过多的层传输到GPU的过程也会消耗一定的时间,此外也造成合成阶段的时间占用较长,因此并不是独立的层越多越好。最好的做法是对那些可能动画的元素设置属性,并在动画结束后就移除这个属性。
SVG图表动画性能的优化
根据以上的分析,总结GPU实现动画的优缺点:
优点:
利用了GPU合成图层实现动画,可以做到动画平滑、流畅
动画合成工作在GPU线程,不会被CPU的js运行阻塞
缺点:
绘图层必须传输到GPU,当图层较多时传输过程可能会导致渲染缓慢
每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃
复合层合成需要更多的时间
对于一般的HTML的元素,遵循上述的方法就可以了,但有数大屏中的图表是使用SVG元素来绘制的,由于并不是标准的DOM元素,Chrome并不能支持SVG元素的硬件加速,即使设置了transform、will-change等属性,单个的SVG元素也不能作为单独的层进行绘制。
从这个使用transform实现的SVG动画可以看到即使使用了transform,动画部分的页面仍然需要重绘。
对于下面这个有多个图表的页面,其中只有一个图表有动画,在动画过程中,如果只有一个图层,那么需要将整个图层进行重绘,虽然现在Chrome已经可以智能选择最小的重绘区域进行增量绘制,但当图层较大时这样的判断也会造成一定的开销。因此可以考虑将有动画的图标单独放在一个图层中,其他没有动画的图表和页面是的其他元素仍然和背景在一个图层中:
分别对分层的和未分层的页面进行5次性能测试,将分析结果进行对比:
可以看出,分层后每帧动画绘制的总时间有明显下降,主要体现在绘制(painting)过程中:其中paint时间的减少应该是因为每次重绘的图层面积只有图表区域,因此需要更少的时间来判断需要更新纹理的区域;composite layers时间的减少,应该主要是由于需要绘制的图层的减小,导致纹理上传GPU和调用OpenGL绘制接口的时间减少了;而rendering时间的增加原因还需要进一步的调研。
在实际使用中,可以在某张图表需要动画时,设置SVG标签的will-change属性,将SVG元素提升到独立的层,来减少动画绘制时间;而图表没有动画时则不需要特别处理,避免过度绘制造成的内存占用增加和图层传输、合成时间的增加。
参考
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 用script标签加载