当前位置: 首页 > Web前端 > HTML

Composite

时间:2023-04-02 13:37:07 HTML

前端性能优化摘要:一个网页的展示,简单来说可以认为是经历了JavaScript/Style/Layout/Paint/Composite这几个步骤。本文主要深入到Composite部分,从渲染原理、Composite的原因以及如何优化其性能入手。一个网页的显示,简单来说可以认为是经过以下几个步骤。?JavaScript:一般来说,我们会使用JavaScript来实现一些视觉上的变化效果。比如制作动画或者给页面添加一些DOM元素。?Style:计算样式,这个过程是根据CSS选择器为每个DOM元素匹配对应的CSS样式。在这一步之后,确定应该将哪些CSS样式规则应用于每个DOM元素。?布局:布局。上一步确定了每个DOM元素的样式规则。这一步是具体计算每个DOM元素在屏幕上的最终显示尺寸和位置。网页中元素的布局是相对的,一个元素布局的变化会引发其他元素布局的变化。例如,一个元素的宽度变化会影响它的子元素的宽度,而它的子元素宽度的变化会继续影响它的孙元素。所以对于浏览器来说,布局过程经常发生。?绘画:绘画本质上是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,即一个DOM元素的所有视觉效果。通常,此绘制过程是在多个图层上完成的。?Composite:渲染层合并,从上一步我们可以看出,页面中DOM元素的绘制是在多个层上进行的。在完成每一层的绘制过程后,浏览器将所有层按逻辑顺序组合成一层显示在屏幕上。这个过程对于有重叠元素的页面尤为重要,因为一旦图层合并顺序错误,元素就会显示异常。当然,在本文中我们只关注Composite部分。浏览器渲染原理在讨论Composite之前,有必要简单了解一下部分浏览器的渲染原理(本文仅针对Chrome),以方便后续一些概念的理解。详情请参考Chrome中的GPU加速合成注意:由于Chrome对Blank引擎的一些实现进行了修改,我们之前知道的一些类名发生了变化,比如RenderObject变成了LayoutObject,RenderLayer变成了PaintLayer。感兴趣的人请参阅SlimmingPaint。在浏览器中,页面内容被存储为一个由Node对象组成的树状结构,也就是DOM树。每一个HTMLelement元素都有一个Node对象与之对应,DOM树的根节点永远是DocumentNode。这一点相信大家都很熟悉,但其实从DOM树到最终的渲染,都需要进行一些转换映射。DOM树中从Nodes到LayoutObjects的每个Node节点都有一个对应的LayoutObject。LayoutObject知道如何在屏幕上绘制Node的内容。从LayoutObjects到PaintLayers一般来说,坐标空间相同的LayoutObjects属于同一个渲染层(PaintLayer)。PaintLayer最初是用来实现堆叠竞赛(stackingcontext),保证页面元素按正确的顺序合成(composite),从而使元素的重叠和半透明元素能够正确显示。因此,一个满足形成堆叠上下文条件的LayoutObject,必然会为其创建一个新的渲染层。当然还有其他一些特殊情况,就是为一些特殊的LayoutObjects创建一个新的渲染层,比如overflow!=visibleelements。根据创建PaintLayer的不同原因,可以分为三种常见的类别:?NormalPaintLayer根元素(HTML)具有明确的定位属性(relative、fixed、sticky、absolute)透明(不透明度小于1)具有CSS过滤器(filter)有CSSmask属性有CSSmix-blend-mode属性(非正常)有CSStransform属性(不是无)backface-visibility属性有hiddenCSSreflection属性有CSScolumn-count属性(非自动)或widthproperty(notauto)当前有动画应用于opacity,transform,filter,backdrop-filterOverflowClipPaintLayeroverflowisnotvisibleNoPaintLayer不需要paint的PaintLayer,比如没有视觉属性(背景,颜色,阴影等)的PaintLayer空的分区。满足上述条件的LayoutObject将拥有一个独立的渲染层,而其他LayoutObject将与其第一个具有渲染层的父元素共享一个。PaintLayers到GraphicsLayers等一些特殊的渲染层会被认为是合成层(CompositingLayers)。合成层有一个单独的GraphicsLayer,而其他不是合成层的渲染层与第一个GraphicsLayer共享一个父层。每个GraphicsLayer都有一个GraphicsContext,负责输出图层的位图。位图存储在共享内存中,并作为纹理上传到GPU。最后,GPU合成多个位图,然后绘制到屏幕上,此时,我们的页面就显示在屏幕上了。将渲染层提升为复合层有几个原因:注意:将渲染层提升为复合层是有前提的。渲染层必须是SelfPaintingLayer(基本上可以看成是上面介绍的NormalPaintLayer)。下面讨论的渲染层在渲染层为SelfPaintingLayer的前提下提升为复合层。?直接原因(directreason)硬件加速的iframe元素(比如页面中嵌入了iframe的复合层)demovideo元素覆盖视频控制栏3D或硬件加速的2DCanvas元素演示:普通的2DCanvas会notimprove对于复合层demo:3DCanvas提升为复合层硬件加速插件,如flash等,在DPI较高的屏幕上,fix定位的元素会自动提升到复合层.但是在DPI较低的设备上就不是这样了,因为这个渲染层的改进会把字体渲染方式从亚像素变成灰度(具体可以参考:TextRendering)。Fliter和backdropfilter应用动画或过渡(需要是激活的动画或过渡,当动画或过渡效果还没有开始或结束时,promotion复合层也会失效)demo:animationdemo:transitionwill-change设置为opacity,transform,top,left,bottom,right(top,left等需要设置明确的定位属性,比如relative等)demo后代元素的原因是复合层的后代,有transform,opactiy(小于1),mask,fliter,reflection属性demo有合成层后代同时overflow不可见(如果SelfPaintingLayer本身是因为定位因素明确生成的,z-index需要不是auto)demo有合成层descendantsanditselfisfixedPositioningdemohas3Dtransfromsyntheticlayerdescendantsanditself它具有preserves-3dpropertydemo具有3Dtransfromcompositinglayer'soffspringanditselfhasperspectivepropertydemoOverlapReason为什么会出现重叠的合成层?举个简单的栗子。蓝色矩形与绿色矩形重叠,它们的父级是GraphicsLayer。此时,假设绿色矩形是一个GraphicsLayer。如果overlap不能提升合成层,蓝色矩形不会提升到合成层,会和父元素共享一个GraphicsLayer。这时候渲染顺序就会出错,所以为了保证渲染顺序,overlap也成为了合成层的原因,就是下面的正常情况。当然重叠的原因也会细分为几类,接下来我们会详细看。在合成层之上重叠或部分重叠。如何算重叠?最常见也好理解的就是元素的边框框(content+padding+border)与合成层重叠,如:demo。当然,margin区域的重叠是无效的(demo)。还有一些不常见的情况,可以认为是与复合层重叠的情况,如下:滤镜效果与复合层重叠demotransform变换后与复合层重叠demooverflowscroll与复合层重叠。即如果overflowscroll的一个元素(不管overflow:auto还是overflow:scrill,只要能滚动即可)与复合层重叠,其可视子元素也与复合层重叠。在(假设重叠)。这个理由听起来有点假。什么是假设重叠?其实更容易理解,比如一个元素的CSS动画效果,在动画运行过程中,这个元素可能会和其他元素重叠。鉴于这种情况,复合层assumedOverlap是有原因的,如示例:demo。在此演示中,动画元素与其兄弟元素在视觉上没有重叠,但由于assumedOverlap,其兄弟元素仍被提升为复合层。需要注意的是,由于这个原因,有一个非常特殊的情况:如果合成层有一个inlinetransform属性,它会导致其兄弟渲染层呈现重叠,从而将其提升为一个合成层。例如:演示。层压缩基本上是改进某些合成层的常见原因。如上所述,你会发现由于重叠的原因,可能会随便生成大量的合成层,每个合成层都会消耗CPU和内存资源,那岂不是严重影响页面性能。浏览器也考虑到了这一点,所以有层压缩(LayerSquashing)处理。如果多个渲染层与合成层重叠,这些渲染层将被压缩到单个GraphicsLayer中,以防止由于重叠而可能出现的“层爆炸”。详情请看下面的demo。一开始蓝色方块因为translateZ被提升为一个复合层,其他方块元素因为overlapping被压缩在一起,大小就是包含这三个方块的矩形的大小。当我们hover绿色方块的时候,我们会给它设置translateZ属性,使得绿色方块提升到复合层,剩下的两个一起压缩,大小缩小到包含这两个的矩形的大小广场。当然,浏览器的自动图层压缩也不是万能的。在很多特定的情况下,浏览器无法进行图层压缩,如下图,这些情况应该尽量避免。(注意:以下情况是基于重叠的原因)?不能进行打乱渲染顺序的压缩(squashingWouldBreakPaintOrder)video所在(squashingVideoIsDisallowed)demoiframe和plugin的渲染层无法压缩,其他渲染层无法压缩到所在的合成层(squashingLayoutPartIsDisallowed)demo无法压缩带有反射属性的渲染层(squashingReflectionDisallowed)demoCannot被压缩具有混合模式属性的渲染层(squashingBlendingDisallowed)演示?当渲染层具有与合成层不同的剪辑容器时,渲染层无法被压缩(squashingClippingContainerMismatch)。?相对于合成层滚动的渲染层不能被压缩(scrollsWithRespectToSquashingLayer)?当渲染层和合成层有不同的不透明度的祖先层时(一个设置了不透明度且小于1,一个不设置不透明度被认为是不同的),渲染层无法压缩(squashingOpacityAncestorMismatch,同squashingClippingContainerMismatch)demo当渲染层与复合层有不同的transform祖先层时,渲染层无法压缩(squashingTransformAncestorMismatch,同上)demo当渲染层有不同的transform时ancestorlayersfromthecompositelayer当有filter的祖先层时,渲染层不能被压缩(squashingFilterAncestorMismatch,同上)。才可以压缩demo如何查看合成层使用ChromeDevTools工具查看页面中的合成层。更简单的方法是打开DevTools,勾选Showlayerborders,页面上的复合图层会被黄色边框框住。当然,更详细的信息可以通过Timeline查看。每个单独的帧,查看每个帧的渲染细节:点击后,您会在视图中看到一个新的选项卡:Layers。单击图层选项卡,您将看到一个新视图。在此视图中,您可以扫描、缩放等此帧中的所有合成层,同时还可以查看创建每个渲染层的原因。使用此视图,您可以准确了解页面中有多少合成层。如果您正在分析页面滚动或渐变效果并且发现合成过程花费了太多时间,那么您可以从这个视图中看到页面中有多少渲染层,为什么创建它们,从而优化合成层的数量.性能优化升级到合成层,简而言之有以下优点:?合成层的位图将由GPU合成,比CPU处理更快?需要重绘时,只需要重绘本身,并且不会影响其他图层?对于变换和不透明效果,不会触发布局和绘制。合成层的使用对提升页面性能有很大的作用,所以我们也总结了一些优化建议。改进动画效果的元素合成层的好处是不会影响其他元素的绘制。因此,为了减少动画元素对其他元素的影响从而减少绘制,我们需要将动画效果中的元素提升到一个合成层。增强合成层的最佳方法是使用CSSwill-change属性。从上一节关于合成层的原因我们可以知道,设置will-change为opacity、transform、top、left、bottom、right可以将元素提升为合成层。其兼容性如下:对于目前不支持will-change属性的浏览器,常用的是使用3Dtransform属性强制提升到复合层:但需要注意的是不要创建太多渲染层。因为每创建一个新的渲染层,就意味着新的内存分配和更复杂的层管理。我们稍后会详细讨论。如果您已将元素放入新的合成层,请使用时间轴查看这样做是否真的提高了渲染性能。不要盲目改进合成层,一定要分析它的实际性能。使用变换或不透明度来实现动画效果。文章开头我们讲了页面所经历的渲染管线。其实从性能上来说,理想的渲染管线是没有布局和绘制环节的,只需要做合成层的合并。就是这样:为了达到上面的效果,你需要只使用那些只触发Composite的属性。目前,只有两个属性满足此条件:transforms和opacity。有关更多详细信息,请参阅CSS触发器。注意:元素被提升为复合层后,transform和opacity不会触发paint。如果不是复合层,它仍然会触发绘制。详情请看下面两个demo。?demo1:transform?demo2:opacity可以看到目标元素还没有提升到合成层,transform和opacity还是会触发paint。缩小绘图区域。对于不需要重绘的区域,尽量避免绘制,减少绘制面积。例如,页面顶部的固定导航标题。在页面内容的某个区域重绘时,包括修复的header在内的整个屏幕也会被重绘,看demo,结果如下:对于固定区域,我们期望它不会重绘,所以可以通过前面的方法提升为一个独立的复合层。减少绘图区域需要仔细分析页面,区分绘图区域,减少重绘区域甚至避免重绘。合理管理合成层看完上面的文章,你会发现改进合成层会获得更好的性能。这看起来很诱人,但问题是创建一个新的合成层并不是免费的,它会消耗额外的内存和管理资源。事实上,在内存资源有限的设备上,合成层带来的性能提升可能远远落后于过多的合成层开销对页面性能的负面影响。同时,由于每个渲染层的贴图都需要上传到GPU进行处理,所以我们还需要考虑CPU和GPU之间的带宽问题,GPU有多少内存可以用来处理这些贴图。对于合成层的内存占用问题,我们简单做了几个demo进行验证。在演示1和演示2中,将创建2000个相同的div元素。不同的是demo2中的元素是通过will-change提升到复合层的,但是两个demo页面的内存消耗有明显的不同。防止层爆炸通过前面的介绍,我们知道与复合层重叠,也会将元素提升到复合层。浏览器中虽然有层压缩机制,但也有很多情况无法压缩。也就是说,除了我们明确声明的复合层之外,还有可能因为重叠的原因,不经意间生成了一些意想不到的复合层。在极端情况下,可能会产生大量额外的复合层,导致层爆炸。我们简单地写了一个demo,它是一个极端点,但实际上在我们的页面中更常见。在demo中,.animating的复合层正在运行动画,会因为上面介绍的assumedOverlap的原因,导致.inner元素被提升到复合层。同时将.inner.box的父元素设置为overflow:hidden,导致.inner的合成层因为squashingClippingContainerMismatch无法压缩,存在层爆炸的问题。这种情况在我们的业务中还是很常见的。比如slider+list的结构,一旦层压缩得不到满足,就很容易出现层爆炸的问题。解决层爆炸的问题,最好的解决办法就是打破重叠条件,也就是让其他元素不与复合层元素重叠。对于上面的例子,我们可以增加.animation的z-index。此时在修改的demo中,只有.animating提升到了合成层,如下:同时,内存占用也比之前低了很多。如果由于视觉需要等因素,必须在复合层上覆盖其他元素,则尽量避免不可压缩层的出现。针对上例中无法进行图层压缩(squashingClippingContainerMismatch)的情况,我们可以去掉.box的overflow:hidden,这样就可以使用浏览器的图层压缩了。修改完demo后,此时因为第一个.box因为squashingLayerIsAnimating无法压缩,其他的一起压缩。同时,内存占用比以前低很多。最后,之前无线开发的时候,大部分人喜欢用translateZ(0)做所谓的硬件加速来提升性能,但是没有所谓的性能优化的“灵丹妙药”,translateZ(0)是没有的,列在这篇文章也不是优化建议。不管对页面具体分析如何,任何性能优化都是站不住脚的,盲目地使用一些优化措施可能会适得其反。因此,分析页面的实际性能并不断改进测试才是正确的优化方式。本文作者:wibud阅读原文。本文为云栖社区原创内容,未经许可不得转载。