估计对各位而言,渲染图层 和 复合图层 这两个概念是颇为陌生的。当然,阅读本文也需要对浏览器的渲染流程有一定的了解,本文讲解的对象主体是谷歌浏览器。
|
谷歌浏览器渲染流程 |
具体过程不过多解释,有兴趣看下《页面的渲染流程(Chrome)》
图片来源 The Anatomy of a Frame
渲染图层(Render Layer)与复合图层(Graphics Layer)关系 |
两者都是为了让HTML元素在2D平面堆叠出3D的视觉效果。
图片来源 GPU Accelerated Compositing in Chrome
简单的理解就是,拥有层叠上下文属性的元素会生成一个新的层叠上下文对象,每个层叠上下文对象都是一个渲染图层,渲染图层与复合图层是不同的概念,渲染图层更像是一个纯二维的概念,无论其怎么层叠覆盖最终都归依于根层叠上下文。而复合图层则完全脱离根层叠上下文,相当于开辟新的位面。
|
我们假定用户正面向(浏览器)视窗或网页,而 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想。众 HTML 元素基于其元素属性按照优先级顺序占据这个空间。
简单点的理解就是,让HTML元素在2D平面堆叠出3D的视觉效果,根据层叠规则将哪个元素置于视觉最近处,哪个次之,以此类推。
形成层叠上下文 |
- 文档根元素 ,称为“根层叠上下文”。
- position属性的值不为static且z-index值不为auto
- opacity属性值小于1的元素
- flex/grid布局且z-index不为auto的元素
- will-change值设定任意属性且值为非初始化值
- transform值不为none
- filter值不为none
更多可形成层叠上下文的属性请点击
层叠上下文特性 |
1.层叠上下文可以包含在其他层叠上下文中,并能一起组建一个层叠上下文的层级(出现层级关系)
在根层叠上下文中,比如我们给某元素添加 transform:rotate(7deg);
属性,该元素随即形成新的一个层叠上下文,那么根层叠上下文就与新的层叠上下文出现了层级关系。
2.每个层叠上下文都完全独立于它的兄弟元素:当处理层叠时只考虑子元素
两个兄弟div,其中一个div形成了层叠上下文,处理层叠关系的时候这两个div是没有联系的,你在你的区域怎么样都不会影响我正常排布,层叠上下文的元素只考虑自己的子元素
3.每个层叠上下文都是自包含的:当一个元素的内容发生层叠后,该元素将被作为整体在父级层叠上下文中按顺序进行层叠。
4. 没有创建自己的层叠上下文的元素会被父层叠上下文同化。
层叠顺序 |
层叠等级的比较只有在当前层叠上下文元素中才有意义。不同层叠上下文中比较层叠等级是没有意义的。
在同一个层叠上下文中且不考虑CSS3的情况下,当元素发生层叠时,层叠顺序遵循下图中的规则 :
|
- Chrome 拥有两套不同的渲染路径(rendering path):硬件加速路径和旧软件路径(older software path)
- Chrome 中有不同类型的层: RenderLayer(渲染层)和GraphicsLayer(图形层,也称复合图层),只有 GraphicsLayer 是作为纹理(texture)上传给GPU的。
- 什么是纹理?可以把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmapimage)
- Chrome 使用纹理从 GPU上获得大块的页面内容。通过将纹理应用到一个非常简单的矩形网格就能很容易匹配不同的位置(position)和变形(transformation)。这也就是3D CSS 的工作原理,它对于快速滚动也十分有效。
层的分类 |
浏览器中图层一般包含两大类:渲染图层(普通图层)以及复合图层
- 渲染图层,是页面普通的文档流。我们虽然可以通过绝对定位,相对定位,浮动定位脱离文档流,但它仍然属于默认复合层(根层叠上下文),共用同一个绘图上下文对象(GraphicsContext)。
- 复合图层,又称图形层。它会单独分配系统资源,每个复合图层都有一个独立的GraphicsContext。(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)
渲染图层与复合图层关系 |
某些特殊的渲染层会被提升为复合成层(Compositing Layers),复合图层拥有单独的 GraphicsLayer,而其他不是复合图层的渲染层,则跟随第一个拥有 GraphicsLayer 的父层。
每个GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后显示到屏幕上。
复合图层创建标准 |
- 3D转换:translate3d,translateZ依此类推;
<video>,<canvas>和<iframe>
元件;- transform和opacity经由Element.animate();
- transform和opacity经由СSS过渡和动画;
- 有合成层后代同时本身 fixed 定位
- will-change;
- 拥有加速 CSS 过滤器的元素filter;
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
- 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
等等具体可看 performance-composite
复合图层的作用?(为什么硬件加速会使页面流畅) |
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快(毕竟该硬件专为处理图像运算的工作负载而设计)
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 、layer和 paint,直接进入合成线程处理
- CPU 和 GPU 之间的并行性,可以同时运行以创建高效的图形管道。
注意 |
1.提升到合成层后合成层的位图会交GPU处理,但请注意,仅仅只是合成的处理需要用到GPU,生成合成层的位图处理是需要CPU。
2.当需要 repaint 的时候可以只 repaint 本身,不影响其他层,但是 paint 之前还有 style, layout 渲染过程,那就意味着即使合成层只是 repaint 了自身,但 style 和 layout 本身就很占用时间。
3.通过transform
和opacity
创建的复合图层不会引发 layout 和paint,其他的属性暂未确定,因为这两个没有发生形变和rgb变化。
总结合成层的优势:一般一个元素开启硬件加速后会变成合成层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。
性能优化点 |
1.提升动画效果的元素 合成层的好处是不会影响到其他元素的绘制。提升合成层的最好方式是使用 CSS 的 will-change属性。从上一节合成层产生原因中,可以知道 will-change 设置为opacity、transform、top、left、bottom、right 可以将元素提升为合成层。
2.使用 transform 或者 opacity 来实现动画效果, 这样只需要做合成层的合并就好了。
3.减少绘制区域 对于不需要重新绘制的区域应尽量避免绘制,以减少绘制区域,比如一个 fix 在页面顶部的固定不变的导航header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,我们期望其并不会被重绘,因此可以通过之前的方法,将其提升为独立的合成层。减少绘制区域,需要仔细分析页面,区分绘制区域,减少重绘区域甚至避免重绘。
复合图层的使用注意事项 |
1.尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡
2.层爆炸,由于某些原因可能导致产生大量不在预期内的合成层,虽然有浏览器的层压缩机制,但是也有很多无法进行压缩的情况,这就可能出现层爆炸的现象(简单理解就是,很多不需要提升为合成层的元素因为某些不当操作成为了合成层)。使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显。
复合图层会占用系统 RAM 与 GPU(在移动设备上尤其有限)的内存,并且拥有大量的层会因为记录哪些是可见的而引入额外的开销。许多层还会因为过大与许多内容重叠而导致“过度绘制(overdraw)”的情况发生,从而增加栅格化的时间。
|
为什么transform动画没有触发repaint呢? |
简而言之,transform动画由GPU控制,支持硬件加载,并不需要软件方面的渲染。Chrome 可以使用早已作为纹理而存在于 GPU 中的层来重新复合,但会使用不同的复合属性。
查看复合图层 |
Chrome源码调试 -> More Tools -> Layers
<!doctype html>
<html>
<body>
<div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
<div>where are you</div>
</body>
</html>
查看硬件加速层 |
Chrome源码调试 -> More Tools -> Rendering -> Layer borders
📌橘黄色框部分就是硬件加速层
证明硬件加速后CSS动画不会引起重绘? |
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div>I am a strange root.</div>
</body>
</html>
可以观察到动画在动,而绘制指令并没有发生改变。也可以调节绘制时间戳看绘制指令变动。
举个反例:
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div id="foo">I am a strange root.</div>
<input id="paint" type="button" value="repaint">
<script>
var w = 200;
document.getElementById('paint').onclick = function() {
document.getElementById('foo').style.width = (w++) + 'px';
}
</script>
</body>
</html>
Chrome源码调试 -> More Tools -> Layers
一开始绘制指令没变,点击按钮会为div宽度加1px,关闭profiler后重新打开,可看到绘制指令增加了
=============================================================================================================
参考文档:
gpu-animation-doing-it-right
performance-composite
Accelerated Rendering in Chrome