关注公众号 前端开发博客 ,回复“ 加群 ”
加入我们一起学习,天天进步
作者 : 晨风明悟
链接:https://zhuanlan.zhihu.com/p/39880024
第一篇:网页渲染性能优化 —— 渲染原理
第二篇:你需要知道的网页渲染性能优化方法(上)
这是第三篇,以下为正文
Composite 的优化
终于,我们到了像素管道的末尾。对于这一部分的优化策略,我们可以从为什么需要 Composited Layer(Graphics Layer)来入手。这个问题我们在构建 Graphics Layer Tree 的时候,已经说明过,现在简单回顾一下:
根据 Composited Layer 的这两个特点,可以总结出以下几点优化措施。
使用 transform 和 opacity 属性来实现动画
上文我们说过像素管道的 Layout 和 Paint 部分是可以略过,只进行 Composite 的。实现这种渲染方式的方法很简单,就是使用只会触发 Composite 的 CSS 属性;目前,满足这个条件的 CSS 属性,只有 transform 和 opacity。
使用 transform 和 opacity 需要注意的是:元素必须是 Composited Layer;如果不是,Paint 还是会照常触发(Layout 要看情况,一般 transform 会触发)。来看一个例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .div { width: 100px; height: 100px; background-color: #f00; /* will-change: transform; */ } </style> <title>性能优化</title> </head> <body> <div class="div"></div> <script> const div = document.querySelector(".div"); const run = () => { div.style.transform = "translate(0, 100px)"; }; setTimeout(run, 2000); </script> </body> </html>
我们将使用 transform 来向下位移,开始我们先不把 div 节点提升为 Composited Layer;通过下图可以看到:还是会触发 Layout 和 Paint 的。
这时,把 div 节点提升为 Composited Layer,我们发现 Layout 和 Paint 已经被略过了,符合我们的预期。
减少绘制的区域
如果不能避免绘制,我们就应该尽可能减少需要重绘的区域。例如,页面顶部有一块固定区域,当页面某个其他区域需要重绘的时候,很可能整块屏幕都要重绘,这时,固定区域也会被波及到。像这种情况,我们就可以把需要重绘或者受到影响的区域提升为 Composited Layer,避免不必要的绘制。
提升成 Composited Layer 的最佳方式是使用 CSS 的 will-change 属性,它的详细说明可以查看 MDN 的文档。
.element { will-change: transform; }
对于不支持的浏览器,最简单的 hack 方法,莫过于使用 3D 变形来提升为 Composited Layer 了。
.element { transform: translateZ(0); }
根据上文所讲的例子,我们尝试使用 will-change 属性来让固定区域避免重绘。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .div { width: 100px; height: 100px; background-color: #f00; } .header { position: fixed; z-index: 9999; width: 100%; height: 50px; background-color: #ff0; /* will-change: transform; */ } </style> <title>性能优化</title> </head> <body> <header class="header">固定区域</header> <div class="div">变动区域</div> <script> const div = document.querySelector(".div"); const run = () => { div.style.opacity = 0.5; }; setTimeout(run, 2000); </script> </body> </html>
首先,我们来看下没有经过优化的情况;顺带说明查看浏览器一帧绘制详情的过程。
点击设置(标记 1),开启绘制分析仪(标记 2)。
启动 Record(标记 3),获取到想要的信息后,点击 Stop(标记 4), 停止 Record。
点击这一帧的 Paint(标记 5)查看绘制详情。
切换到 Paint Profiler 选项卡(标记 6),查看绘制的步骤。
通过上面的图片(标记 7 和标记 8)可以看到,固定区域的确被波及到,并且触发重绘了。我们再对比使用 will-change 属性优化过的情况,发现固定区域没有触发重绘。
并且,我们也可以通过一帧(标记 1)的布局详情(标记 2),查看固定区域(标记 3)是不是提升成 Composited Layer(标记 4),才避免的不必要绘制。
合理管理 Composited Layer
提升成 Composited Layer 的确会优化性能;但是,要知道创建一个新的 Composited Layer 必须要额外的内存和管理,这是非常昂贵的代价。所以,在内存资源有限的设备上,Composited Layer 带来的性能提升,很可能远远抵不上创建多个 Composited Layer 的代价。同时,由于每一个 Composited Layer 的位图都需要上传到 GPU;所以,不免需要考虑 CPU 和 GPU 之间的带宽以及用多大内存处理 GPU 纹理的问题。
我们通过 1000 个 div 节点,来对比普通图层与提升成 Composited Layer 之后的内存使用情况。可以发现差距还是比较明显的。
最小化提升
通过上文的说明,我们知道 Composited Layer 并不是越多越好。尤其是,千万不要通过下面的代码提升页面的所有元素,这样的资源消耗将是异常恐怖的。
* { /* or transform: translateZ(0) */ will-change: transform; }
最小化提升,就是要尽量降低页面 Composited Layer 的数量。为了做到这一点,我们可以不把像 will-change 这样能够提升节点为 Composited Layer 的属性写在默认状态中。至于这样做的原因,我会在下面讲解。
看这个例子,我们先把 will-change 属性写在默认状态里;然后,再对比去掉这个属性后渲染的情况。
.box { width: 100ox; height: 100px; background-color: #f00; will-change: transform; transition: transform 0.3s; } .box:hover { transform: scale(1.5); }
使用 will-change 属性提升的 Composited Layer:
普通图层:
我们发现区别仅在于,动画的开始和结束,会触发重绘;而动画运行的时候,删除或使用 will-change 是没有任何分别的。
我们在构建 Graphics Layer Tree 的时候讲到过这样一条理由:
对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition(需要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,提升的 Composited Layer 会恢复成普通图层)。
这条理由赐予了我们动态提升 Composited Layer 的权利;因此我们应该多利用这一点,来减少不必要的 Composited Layer 的数量。
防止层爆炸
我们在 Graphics Layer Tree 中介绍过层爆炸,它指的是由于重叠而导致的大量额外 Composited Layer 的问题。浏览器的层压缩可以在很大程度上解决这个问题,但是,有很多特殊的情况,会导致 Composited Layer 无法被压缩;这就很可能产生一些不在我们预期中的 Composited Layer,也就是说还是会出现大量额外的 Composited Layer。
在层压缩这一节,我们已经给出了使用层压缩优化的例子,这里就不再重复了。下面再通过解决一个无法被层压缩的例子,来更为深入的了解如何防止层爆炸。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .animating { width: 300px; height: 30px; line-height: 30px; background-color: #ff0; will-change: transform; transition: transform 3s; } .animating:hover { transform: translateX(100px); } ul { padding: 0; border: 1px solid #000; } .box { position: relative; display: block; width: auto; background-color: #00f; color: #fff; margin: 5px; overflow: hidden; } .inner { position: relative; margin: 5px; } </style> <title>性能优化</title> </head> <body> <div class="animating">动画</div> <ul> <li class="box"> <p class="inner">提升成合成层</p> </li> <li class="box"> <p class="inner">提升成合成层</p> </li> <li class="box"> <p class="inner">提升成合成层</p> </li> <li class="box"> <p class="inner">提升成合成层</p> </li> <li class="box"> <p class="inner">提升成合成层</p> </li> </ul> </body> </html>
当我们的鼠标移入 .animating 元素的时候,通过查看 Layers 面板,可以很清晰的看到出现的大量 Composited Layer。
这个例子虽然表面上看起来没有发生重叠;但是,因为在运行动画的时候,很可能与其他元素造成重叠,所以 .animating 元素会假设兄弟元素在一个 Composited Layer 之上。这时,又因为 .box 元素设置了 overflow: hidden; 导致自己与 .animating 元素有了不同的裁剪容器(Clipping Container),所以就出现了层爆炸的现象。
解决这个问题的办法也很简单,就是让 .animating 元素的 z-index 比其他兄弟元素高。因为 Composited Layer 在普通元素之上,所以也就没有必要提升普通元素,修正渲染顺序了。这里我在顺便多说一句,默认情况下 Composited Layer 渲染顺序的优先级是比普通元素高的;但是在普通元素设置 position: relative; 之后,因为层叠上下文,并且在文档流后面的原因,所以会比 Composited Layer 的优先级高。
.animating { position: relative; z-index: 1; ... }
当然,如果兄弟元素一定要覆盖在 Composited Layer 之上,那我们也可以把 overflow: hidden; 或者 position: relative; 去掉,来优化 Composited Layer 创建的数量或者直接就不创建 Composited Layer。
参考资料
使用CSS3 will-change提高页面滚动、动画等渲染性能
总结
本文首先讲了渲染需要构建的一些树,然后通过这些树与像管道各部分的紧密联系,整理了一些优化措施。例如,我们对合成所进行的优化措施,就是通过 Graphics Layer Tree 来入手的。
优化也不能盲目去做,例如,提升普通图层为 Composite Layer 来说,使用不当,反而会造成非常严重的内存消耗。应当善加利用 Google 浏览器的调试控制台,帮助我们更加详尽的了解网页各方面的情况;从而有针对性的优化网页。
文章参考了很多资料,这些资料都在每一节的末尾给出。它们具有非常大的价值,有一些细节,本文可能并没有整理,可以通过查看它们来更为深入的了解。