当前位置: 首页 > 科技观察

抖音三方面:硬件加速中的“层”和堆栈上下文中的“层”是一回事吗?

时间:2023-03-18 00:39:12 科技观察

大家好,我是念念!这篇文章是关于浏览器渲染中的“分层”和硬件加速的。我会澄清:什么是硬件加速?合成层的“层”与堆叠上下文的“层”是否相同?什么是层爆炸和层压缩?据说可以减少回流和重绘,如何使用硬件加速来实现?页面渲染的过程首先,我们来回顾一个老套路:页面渲染的过程。简单的说,在第一次渲染的时候会按照下面的步骤来构建DOM树;风格计算;布局定位;层分层;图层绘制;复合显示;当改变CSS属性时,重新渲染会分为“回流”、“重绘”和“直接合成”三种情况,分别从“布局定位”/“图层绘制”/“合成显示”开始,以及然后走上面的流程。一个元素的CSS会发生什么,取决于它属于以上哪种情况:reflow(也叫重排):元素的位置和大小的变化导致其他节点被链接,需要重新计算布局;重绘:一些不影响布局属性的改变,比如颜色;直接合成:修改合成图层的transform和opacity,只需要再次合并多个图层,然后生成位图,最后显示在屏幕上;上面提到的渲染中的图层在渲染过程中会发生“图层分层”。浏览器中有两种层:“渲染层”和“合成层(也叫合成层)”。很多文章还提到了一个概念叫“图形层”,其实也可以看成是复合层。为了降低理解成本,本文使用“渲染层”和“合成层”这两个术语来描述。开发者工具中的Layers首先直观感受“层”,打开浏览器开发者工具的layers:可以看到AB元素都在最底层,元素C是单独的一层,元素D是层。<样式>正文{边距:0;填充:0;}.box{宽度:100px;高度:100px;背景:rgba(240、163、163、0.4);边框:1px纯粉色;边界半径:10px;文本对齐:居中;}#a{}#b{位置:绝对;顶部:0;左:80;z-指数:2;}#c{位置:绝对;顶部:0;左:160;指数:3;转换:translateZ(0);}#d{位置:绝对;顶部:0;左:240;z-指数:4;}.description{字体大小:10px;}A

Bz-index:2
Cz-index:3
transform:translateZ(0)Dz-index:4正如我之前所说,在浏览器Types中有两层,渲染层和合成层,这里看到的都是合成层。那么,渲染层如何生成,合成层又如何形成呢?渲染层渲染层的概念与“堆栈上下文”密切相关。我之前写过一篇文章,你可以在这里阅读。简单的说,一个带有z-index属性的定位元素生成一个层叠上下文,一个生成层叠上下文的元素生成一个渲染层。还是用上面的例子,BCD的三个元素都是带z-index属性的定位元素(绝对定位),所以三个都组成一个渲染层,加上文档根元素,一共有四个渲染层。(同样,渲染层在开发者工具中是看不到的。)渲染层的形成条件也是堆叠上下文的形成条件。有几种情况:document元素有一个定位元素,其属性为z-index(position:relative|fixed|sticky|absolute)elasticlayout(父元素display:flex|inline-flex)的children,z-index为不自动,不透明度非1的元素转换非无元素过滤非无元素will-change=opacity|改造|filter另外,需要裁剪的元素还会形成一个渲染层,即overflow不可见的元素合成层,不是开发者工具中的渲染层,而是下面要讲的合成层,仅一些特殊的渲染层会被提升为合成层。一般来说有这几种情况:transform:3Dtransform:translate3d,translateZ;将改变:不透明度|改造|过滤器将过渡和动画应用于不透明度|改造|filter(transition/animation)video、canvas、iframe,可见以上条件属于生成渲染层的“加强版”,也就是说形成复合层的条件更加苛刻。还是用一开始的例子,C元素是命中条件1,使用3D变换transform:translateZ(0),所以提升到一个单独的合成层。但是D元素不符合以上任何一条规则,但它也是一个单独的复合层。因为还有一种情况——隐式综合。隐式合成当一个合成层出现时,比它高阶的堆叠元素将被隐式合成。我们为元素C和D设置图层,z-index分别为3和4;并在元素C上使用3D转换将其提升为合成层。此时,层次比它高的D元素被隐式合成,成为复合层。隐式组合的根本原因是元素是堆叠的。为了保证最终的显示效果,浏览器不得不将层级顺序更高的元素拉出来覆盖在已有的复合层上。LayerExplosionandLayerCompression这是我在项目中实际遇到的一个问题:在低端机器上滚动时页面很卡。经过长时间排查,最终发现原因出在隐式合成导致的层爆炸。隐式合成会产生很多意想不到的合成层——页面中所有z-index高于它的节点都会被提升,并且这些合成层非常占用内存和GPU。所以给我们带来的启示就是给合成层一个大的z-index值,避免隐式合成。好在浏览器已经逐步优化,那就是层压缩机制——当多个渲染层与一个复合层重叠时,会自动压缩在一起,避免“层爆炸”带来的损失。硬件加速说了这么多,在实际开发中有什么用呢?也就是说,为什么浏览器需要分层?答案是硬件加速。听起来很厉害,但实际上只是给HTML元素添加某些CSS属性,比如3D变换,提升为复合层独立渲染。之所以叫硬件加速,是因为合成层会交给GPU(显卡)处理。在硬件级别打开插件比在主线程(CPU)上打开效率更高。就像在ipad上画画一样,画家在不同的图层上画线稿和颜色,这样既方便后期修改,又不会影响整体。提升到复合层的元素的回流和重绘只会影响本层,渲染效率会提高。让我们看一个例子。使用动画改变B元素的宽度。通过开发者工具Layers中的“paintcount”,可以看到页面绘制的次数会不断增加,可以直观的感受到页面被“重绘”了。可以注意到,重绘发生在整个图层#document上,也就是整个页面都要重绘。<样式>.box{宽度:100px;高度:100px;背景:rgba(240、163、163、0.4);边框:1px纯粉色;边界半径:10px;文本对齐:居中;#b{位置:绝对;顶部:50;左:50;z-指数:2;动画:宽度变化5s无限;}@keyframeswidth-change{0%{width:80px;}100%{宽度:120px;}}.description{字体大小:10px;}ABanimation:width-change在B元素中添加will-change:transform,开启硬件加速,让它提升为复合层。会发现重绘只发生在这一层,#document层的绘制次数不会一直增加。<样式>.box{宽度:100px;高度:100px;背景:rgba(240、163、163、0.4);边框:1px纯粉色;边界半径:10px;文本对齐:居中;#b{位置:绝对;顶部:50;左:50;z-指数:2;动画:宽度变化5s无限;will-change:变换;}@keyframeswidth-change{0%{width:80px;100%{宽度:120px;}}.description{字体大小:10px;}ABanimation:width-change这就是硬件加速的意义:我们在讲性能优化的时候,常说减少回流和重绘,如果能直接避免当然最好,但如果实在无法避免的话,可以使用硬件加速让这个元素单独回流重绘,减少绘图面积。有得也有失。开启硬件加速后,合成层会交给GPU处理。当层数过多时,会占用大量内存,尤其是在移动端,会造成卡顿,使优化适得其反。硬件加速的正确使用是在渲染效率和性能损失之间找到平衡点,让页面渲染快速流畅,不黑屏。优化渲染性能上文提到,使用硬件加速,可以将需要重新排列/重绘的元素单独提取出来,减少绘制面积。此外,还有几种常见的提高渲染性能的方法:避免重排/重绘,直接合成,合成层transform和opacity的修改直接进入合成阶段;例如,您可以使用transform:translate而不是left/top来修改元素的位置;使用transform:scale而不是width和height修改;注意implicitcomposition,给compositionlayer一个更大的z-index值,虽然大部分浏览器已经实现了压缩layer的能力,但是还是有无法处理的情况,最好的办法是从一开始就避免layersExplosion;减少复合层占用的内存。复合层最大的问题是占用内存较多,内存占用与元素大小成正比。如果要实现一个100X100的元素,可以设置宽高为10px,然后使用transform:scale(10)放大10倍,这样占用的内存只有直接设置的1/100;结语回到前几个问题,文中不难找到答案:硬件加速并不是前端独有的东西,它是一个非常广泛的计算机概念——将软件的工作分配给特定的硬件来完成一个某些任务更有效。对于前端来说,就是利用特定的CSS属性,将元素提升为一个复合层,交给GPU处理;复合层中的“层”可以认为是真正的物理层,浏览器将其分离出来,单独交给GPU处理,而stackingcontext的“层”指的是渲染层,即更像是一个概念层。一个复合层可以包含多个渲染层;layerexplosion是指大量的元素被意外提升为compositeLayer,即隐式合成;layercompression是浏览器对隐式合成的优化,chrome在94版本比较完善;使用transform、opacity代替传统属性实现部分动画,提升为单一的Synthesis层,可以跳过布局计算和重绘,直接合成,避免不必要的reflow和重绘;