这是浏览器渲染引擎的处理过程:
image.png1.接收到文档后,渲染引擎会对HTML文档进行解析生成DOM树、对CSS文件进行解析生成CSSOM树;
2.同时执行页面中的JavaScript代码;
3.最终根据DOM树和CSSOM树,计算样式(Caluclate Style)生成渲染树,【渲染树中,只会包含即将显示在页面中的元素及其样式信息(如head元素、display为hidden的元素就不会包含在渲染树中)】;
4.根据渲染树需要进行布局(layout)来计算每个元素在页面上的位置;
5.接下来渲染引擎开始进行绘制(paint),这一步分为若干阶段:
5.1 根据渲染树将每层(layer)的各个元素绘制
5.2然后将绘制出的若干连续图像进行栅格化(Rasterization),最后将栅格化后的图像合成(composite)为要显示在屏幕上的图像。
5.3对每一层的绘制是由浏览器来完成的;最后的合成是由GPU来完成;而栅格化过程取决于浏览器的设置,chrome默认开启GPU栅格化,否则由CPU进行。
当首次将DOM树构建完成后,每次页面发生改变时进行进行的主要流程为:
javascript -->style-->;ayout -->paint -->compsite
其中CSS动画不会调用JavaScript,我们知道,在渲染中主要消耗时间的是Layout/Reflow和Paint/Repaint的过程,因此要尽量避免和减少这两个阶段的时间。
cpu 和 gpu 的区别
cpu 是通用的,能够执行各种逻辑和运算,而 gpu 则是主要是用于并行计算大批量的重复任务,不能处理复杂逻辑。
cpu 中控制器和缓存占据了很大一部分,而 gpu 中这两部分则很少,但是有更多的核心用于计算。
两者对比的话,cpu 相当于一个大学生,能够解决各种难题,但是计算 1 万个加法就没那么快,而 gpu 就像一帮小学生,解决不了难题,但是计算加法这种就很快,因为人多。
也就是说如果逻辑复杂,那么只能用 cpu,如果只是计算量大,并且每个计算都比较重复,那就比较适合 gpu。
3d 的渲染中有大量这种重复却简单的计算,比如顶点数据和光栅化的像素数据,通过 gpu 就可以并发的一次计算成百上千个。
浏览器在处理下面的 css 的时候,会使用 gpu 渲染:
transform 、opacity 、filter、 will-change
浏览器是把内容分到不同的图层分别渲染的,最后合并到一起,而触发 gpu 渲染会新建一个图层,把该元素样式的计算交给 gpu。
opacity 需要改变每个像素的值,符合重复且大量的特点,会新建图层,交给 gpu 渲染。transform 是动画,每个样式值的计算也符合重复且大量的特点,也默认会使用 gpu 加速。同理 fiter 也是一样
。
这里要注意的是 gpu 硬件加速是需要新建图层的,而把该元素移动到新图层是个耗时操作,界面可能会闪一下,所以最好提前做。will-change 就是提前告诉浏览器在一开始就把元素放到新的图层,方便后面用 gpu 渲染的时候,不需要做图层的新建。
当然,有的时候我们想强制触发硬件渲染,就可以通过上面的属性,比如
will-change: transform;
transform:translate3d(0, 0, 0);
SVG图表动画性能的优化
对于一般的HTML的元素,遵循上述的方法就可以了,但有一些大屏中的图表是使用SVG元素来绘制的,由于并不是标准的DOM元素,Chrome并不能支持SVG元素的硬件加速,即使设置了transform、will-change等属性,单个的SVG元素也不能作为单独的层进行绘制。
对于有多个图表的页面,其中只有一个图表有动画,在动画过程中,如果只有一个图层,那么需要将整个图层进行重绘,虽然现在Chrome已经可以智能选择最小的重绘区域进行增量绘制,但当图层较大时这样的判断也会造成一定的开销。因此可以考虑将有动画的图标单独放在一个图层中,其他没有动画的图表和页面是的其他元素仍然和背景在一个图层中。
在实际使用中,可以在某张图表需要动画时,设置SVG标签的will-change属性,将SVG元素提升到独立的层,来减少动画绘制时间;而图表没有动画时则不需要特别处理,避免过度绘制造成的内存占用增加和图层传输、合成时间的增加。
一些优化建议
1、动画使用 transform 实现
对于一些体验要求较高的关键动画,比如一些交互复杂的玩法页面,存在持续变化位置的 animation 元素,我们最好是使用 transform 来实现而不是通过改变 left/top 的方式。这样做的原因是,如果使用 left/top 来实现位置变化,animation 节点和 Document 将被放到了同一个 GraphicsLayer 中进行渲染,持续的动画效果将导致整个 Document 不断地执行重绘,而使用 transform 的话,能够让 animation 节点被放置到一个独立合成层中进行渲染绘制,动画发生时不会影响到其它层。并且另一方面,动画会完全运行在 GPU 上,相比起 CPU 处理图层后再发送给显卡进行显示绘制来说,这样的动画往往更加流畅。
2、减少隐式合成
虽然隐式合成从根本上来说是为了保证正确的图层重叠顺序,但具体到实际开发中,隐式合成很容易就导致一些无意义的合成层生成,归根结底其实就要求我们在开发时约束自己的布局习惯,避免踩坑。
我们在平时开发中不注意可能造成页面生成了过多的合成层,之前有一个项目,在pc端查看是存在明显卡顿,利用 Chrome Devtools 分析之后发现,页面里边存在的一个带动画 transform 的 button 按钮,提升为了合成层,动画交叠的不确定性使得页面内其他 z-index
大于它但其实并没有交叠的节点也都全部提升为了合成层(这个原因真的好坑)。
这个时候我们只需要把这个动画节点的 z-index
属性值设置得大一些,让层叠顺序高过于页面其他无关节点就行。当然并不是盲目地设置 z-index
就能避免,有时候 z-index
也还是会导致隐式合成,这个时候可以试着调整一下文档中节点的先后顺序直接让后边的节点来覆盖前边的节点,而不用 z-index
来调整重叠关系。方法不是唯一的,具体方式还是得根据不同的页面具体分析。
改善后的页面效果如下,可以看到相比优化前,我们消除了很多无意义的合成层。
3、减小合成层的尺寸
举个简单的例子,分别画两个尺寸一样的 div,但实现方式有点差别:一个直接设置尺寸 100x100,另一个设置尺寸 10x10,然后通过 scale
放大 10 倍,并且我们让这两个 div 都提升为合成层:
<style>
.bottom, .top {
position: absolute;
will-change: transform;
}
.bottom {
width: 100px;
height: 100px;
top: 20px;
left: 20px;
z-index: 3;
background: rosybrown;
}
.top {
width: 10px;
height: 10px;
transform: scale(10);
top: 200px;
left: 200px;
z-index: 5;
background: indianred;
}
</style>
<body>
<div class="bottom"></div>
<div class="top"></div>
</body>
利用 Chrome Devtools 查看这两个合成层的内存占用后发现,.bottom
内存占用是 39.1 KB,而 .top
是 400 B,差距十分明显。这是因为 .top
是合成层,transform 位于的 Composite 阶段,现在完全在 GPU 上执行。因此对于一些纯色图层来说,我们可以使用 width 和 height 属性减小合成层的物理尺寸,然后再用 transform: scale(…)
放大,这样一来可以极大地减少层合成带来的内存消耗。
网友评论