文章

借助神器 DevTools Layers 优化网页滑动性能
2018/08/18

开头

本文重点讨论 layer 而不是层叠上下文,这两者虽然在概念上有重合的地方,但在 Chrome => Developer Tools => Layers 工具里显示的却有所差异:层叠上下文不一定会在这个工具里以图层的方式被表示出来,而 layer 却能在工具里像 PS 图层那样被具象的表示出来,帮助开发者减少想象大量图层重叠所带来的记忆负担。

DevTools

规则探究

  • 有自己的 layer 的元素则必定是一个层叠上下文,而是层叠上下文的元素不一定有自己的 layer。
  • postion:fixed 元素(以下简称 fixed 元素)一旦存在就会立即创建自己的 layer(除此属性之外,还有 postion:sticky , overflow:scroll 等)。
  • 一个 HTML 文档(一个网页)内,一旦 layer 元素大于等于两个,那这些 layer 元素就会进行“攀比”(比较层叠水平),并且他们永远不可能在同一平面上。
  • “攀比”规则:若是这些 layer 元素处在同一层叠上下文内则根据 z-index 与后来居上原则进行层叠水平的排序(排序优先级:z-index > 后来居上)。若是这些 layer 元素各自处在不同层叠上下文内,那他们则有着「天生的差距」,低层叠上下文内的 layer 元素永远无法「攀比」过高层叠上下文内的 layer 元素,哪怕低层叠上下文内的 layer 元素的 z-index 属性是 +999 而高层叠上下文内的 layer 元素的 z-index 是 -999,他们也不可能打破这条「天与地」的差别。
  • 当有 layer 元素在 position:relative/absolute 元素(以下简称 relative/absolute 元素)的层叠水平之下时,relative/absolute 元素会创建他们自己的 layer 把自己「抬起来」。如果不是上述情况,则 relative/absolute 元素永远贴在父 layer 上,其根本也就是 html 这个承载了一切的 layer。
  • z-index:auto 的 layer 元素与 z-index:auto 的 relative/absolute 元素关系较为特殊。当都具备了 z-index:auto 这个属性,且 layer 元素在 HTML 文档中处于 relative/absolute 元素之前但没有重合时relative/absolute 元素附着在它的父 layer 上并不会创建自己的 layer。当它们开始重合时relative/absolute 元素会基于「没有 z-index 数值便依照后来居上」的原则,立即创建自己的 layer 把自己「抬起来」,使自己的的层叠水平处于文档前面那个 layer 元素之上,从而实现了原则里的「后来居上」。

实际意义

之所以要探究清楚浏览器是如何处理 layer 的,是因为这是和你创造出来的网页在滑动时的动画性能是挂钩的,滑动体验卡顿的网页会给用户增加心理负担,谁不想体验和 native app 一样拥有高性能动画表现的网页应用呢? 而如果没有规划好你的各个元素之间 layer 的层叠关系,那么在用户滑动的过程中,浏览器会通过各种新建或删除 layer 的不常规操作来达到你要的视觉效果,虽然最终也是你要呈现的那个层叠关系与画面效果,但在滑动过程中新建或删除 layer 意味着 repaint, 意味着占用了不该占用的处理器资源,当网页内容逐渐多起来了,repaint 积累起来了,计算就容易跟不上从而影响页面动画的流畅度。

具体案例

以为自己的博客为例,我把它定义为 web app 所以希望他有更好的动画性能,不希望有任何不优雅的、违和的 UI 表现存在。

下面这张图片是没有根据 layer 规则优化时的滑动过程。

homepage-bad-gif

而下面这张是根据了 layer 规则优化之后的滑动过程。

homepage-great-gif

不难看出优化之前的图层关系非常凌乱不堪,即使浏览器也通过这么复杂的动态处理达到了你预先设计的堆叠关系,但在你想说出 perfect 之前不妨想一想,频繁的图层变动意味着什么?浏览器在处理你滑动事件的同时,还得忙手忙脚的给用户新建图层,重新在图层上绘制,最后再重新合并为一个总图层呈现给用户。

下图就是浏览器忙前忙后的证据:

browser-render-list-with-repaint

倘若在 More Tools => Rendering 里将 Paint Flashing 勾选上

rendering-setting

那你将会看到更加生动的证据(配合 第一张 GIF 食用):

homepage-repaint-gif

这下可知道浏览器有多「忙」了吧?绿色闪过的地方全是重新绘制、重新合成的部分。而之所以没有 Rendering 设置图中的红色区域主要还是因为目前的这个 Web App 页面结构比较简单,再加上我的 Macbook Pro 性能还没掉队,所以足以应付这种小规模优化不利的场景。

而在优化之后他们就变成了这个样子(配合 第二张 GIF 食用):

homepage-norepaint-gif

绿色提示区域彻底消失貌似是意味着 repaint 事件被彻底避免掉了?让我们来看一看 Performance 重新记录的事件列表证实一下是否如此:

browser-render-list-without-repaint

嗯!的确如此。不光如此,每一项事件的进程时间也变的比之前短了,虽然在我的小型项目上只是零点几毫秒的差距,但在大型项目中还是有意义的。

好奇心阅读