层叠上下文初识 - 利用 chrome devTools 实现 css性能优化

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化

MDN:堆栈上下文

参考博客:

  • CSS中重要的层叠概念

  • 详谈层层合成(composite)

  • 借助神奇 DevTools Layers 优化网页滑动性能

  • GPU加速是什么

在浏览器的元素渲染过程中,除了水平的 XY 布局,还有一条沿着相对于用户的假想 Z 轴。

在水平的 XY 布局中,有 块级上下文(BFC) 等绘制规则,而在 Z 轴上,有 层叠上下文(stacking context) 的绘制规则。

在某些时候,我们对某些元素使用 z-index ,导致某些元素的呈现顺序受其 z-index 值影响。发生这种情况是因为这些元素具有特殊的属性,这些属性导致它们形成了 层叠上下文。从而可以根据 z-index 指定 Z 轴的优先级。

层叠上下文(stacking context,MDN 官网称之为 堆叠上下文,此文统称 层叠上下文

层叠上下文

如何形成层叠上下文

在以下情况下,文档中的任何元素 只要拥有以下条件之一 即可形成堆叠上下文:

根元素:

  • 文档的根元素()。

position

  • 值为absolute 或者relative ,并且z-index的值不为auto的元素。
  • 值为 fixedsticky的元素(sticky可用于所有移动浏览器,但不适用于较旧的桌面浏览器)。

容器:

  • 元素是flex容器的子元素,并且其z-index的值不是auto
  • 元素是grid容器的子元素,并且其z-index值不是auto

opacity

  • opacity的值小于的元素1(请参见不透明度的规范)。

其他:

  • 具有以下任一属性的元素,并且其值不是 none
    • transform
    • filter(滤镜)
    • perspective
    • clip-path(裁剪)
    • mask/ mask-image/mask-border
    • mix-blend-mode的值不是normal的元素。
  • 属性isolation(隔离)的值为isolate的元素。(该属性就是可以主动申请一个 层叠上下文)

以下为当前兼容性不足或者不推荐的但能生成 层叠上下文 的技术:

  • 属性-webkit-overflow-scrolling的值touch为的元素。
  • 属性will-change值不为初始值的元素(重要提示: will-change旨在用作最后手段,以尝试解决现有的性能问题。不应将其用于预期性能问题。)。
  • 属性contain的值layout,或者paint,或包括它们中的一个复合值(即contain: strictcontain: content)。(**这是一项实验性技术,**请在生产中使用它之前仔细检查浏览器兼容性表。)

综上:

  • 层叠上下文可以被其他层叠上下文包含,并一起创建层叠上下文的层次结构

  • 每个层叠上下文和其兄弟独立开来;处理层叠上下文时仅考虑后代元素

  • 每个层叠上下文都是独立的,将元素的内容堆叠之后,将按照父层叠上下文的堆叠顺序考虑整个元素;

    **注意:**比较堆叠顺序时,将只比较同级 DOM 的堆叠顺序,相同情况下再考虑子元素,直到一方比另一方大。

层叠顺序

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第1张图片

别被吓到了,总结下来就一句话:谁大谁上,后来居上

Demo 1

考虑如下两个div

<body>
    <div id="a">
        <div id="a1">div>
        <div id="a2">div>
    div>
body>
#a {
    position: relative;
}

#a1 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: pink;
    top: 0;
    left: 10px;
    z-index: 1;
}
#a2 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: lightblue;
    top: 40px;
    left: 50px;
    z-index: 0;
}

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第2张图片

a1a2在同一级即均为a的子元素,可以看到a1a2均设置了position: absolute属性,故均可设置z-index属性设置在a这个层叠上下文中的层叠顺序。

根据谁大谁上的原则,a1z-indexa2大,故a2在 Z 轴中在 a1之下。

修改之:

#a1 {
    ...
    /* z-index: 1; */
}
#a2 {
    ...
    /* z-index: 0; */
}

现在均没了z-index属性,效果如下:

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第3张图片

根据后来居上的原则,由于都没有指定z-index,故a2后来居上,在 Z 轴中比a1更高。

Demo 2

考虑如下场景

<body>
    <div id="a">
        <div id="a1">div>
        <div id="a2">div>
    div>
    <div id="b">
        <div id="b1">div>
        <div id="b2">div>
    div>
body>
#a {
    ...
}

#a1 {
    ...
}
#a2 {
    ...
}

#b {
    position: relative;
    top: 80px;
}
#b1 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: lightgrey;
    top: 0;
    left: 10px;
}
#b2 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: lightgoldenrodyellow;
    top: 40px;
    left: 50px;
}

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第4张图片

相较于 Demo 1,多出了b和其子元素b1 b2,根据后来居上的原则,在均没有设置z-index的情况下,在 Z 轴方向上b2 > b1 > a2 > a1

现尝试更改 css:

#a {
    ...
    z-index: 2;
}

#a1 {
    ...
    z-index: 2;
}
#a2 {
    ...
    z-index: 1;
}

#b {
    ...
    z-index: 1;
}
#b1 {
    ...
    z-index: 99;
}
#b2 {
    ...
    z-index: 50;
}

注意:根据同级对比的原则,即使b1有 99 的z-index,由于 baz-index 权值小,b整体都比a在 Z 轴上低,ba 比对完之后,才是b内部和 a内部元素的比对。总之,在 Z 轴方向上:a1 > a2 > b1 > b2

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第5张图片

css 性能优化

在浏览器渲染过程中,每个 DOM 节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,即 Z 轴上位于同一平面,就会形成一个 RenderLayers ,也就是渲染层,此文简称 Layer。

Layer 将同一坐标空间的 LayoutObject 组织起来,保证页面元素以正确的顺序合成,这时候就会出现层合成(composite)。

某些特殊的渲染层会被认为是 合成层。合成层拥有单独的处理单元(GraphicsLayer),而其他不是合成层的渲染层 Layer,则和第一个拥有独立处理单元的父层共用一个处理单元。

那么上文的层叠上下文除了能帮助我们正确了解和处理元素在 Z 轴上的展示意外,当我们对层叠上下文进行优化处理时,可以得到 合成层 的 css 性能优化。

但是 层叠上下文 与 Layer 之间的关系可以 笼统地概括为:有自己的 layer 的元素则必定是一个层叠上下文,而是层叠上下文的元素不一定有自己的 layer。

chrome devTools

工欲善其身,必先利其器。在尝试对层叠上下文进行层优化之前,必须要先熟悉一下 chrome 神器中的开发者工具。除了常用的 ElementConsole 以外, 针对这次博客,我们可以在More tools中找到 LayersRenderingPerformance 工具栏。

在使用这三个工具时,我们需要结合 Demo 3

Demo 3

Demo 2的基础上给 a2 添加了动画,并且,将所有的z-index属性去除

#a2 {
    ...
    transition: all 2s linear;
    transform: translate(100px,100px);
}

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第6张图片

乍一看好像没什么,就是一个div 的平移,但是这背后浏览器做了一系列的操作。

Performance

我们可以打开 chrome devTools 的 Performance 面板:

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第7张图片

可以点击实心圆进行录制,期间可以多次刷新浏览器;录制结束:

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第8张图片

可以在 Event Log子选项卡里看到:

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第9张图片

Schedule Style Recalculation -> Recalculate Style -> Update Layer Tree -> Paint -> Composite Layers

基本是这么一个事件循环。

其实基本上,浏览器在每一帧里,会依次执行如下动作:

  1. JavaScipt:JavaScript 实现动画效果,DOM 元素操作等。(Demo 3没有 js ,故这一步没有)
  2. Style(Schedule Style Recalculation、Recalculate Style):确定每个 DOM 元素应该应用什么 CSS 规则。
  3. Layout(Update Layer Tree):计算每个 DOM 元素在最终屏幕上显示的大小和位置。由于 web 页面的元素布局是相对的,所以其中任意一个元素的位置发生变化,都会联动的引起其他元素发生变化,这个过程叫 reflow,即回流
  4. Paint(绘制):在多个层上绘制 DOM 元素的的文字、颜色、图像、边框和阴影等。这个过程会触发对元素的绘制,rePaint,即重绘
  5. Composite(渲染层合并,Composite Layers):按照合理的顺序合并图层然后显示到屏幕上。

首先我们可以了解什么是 16ms 优化

  • 大多数设备的刷新频率是 60 次/秒,(1000/60 = 16.6ms)也就说是浏览器对每一帧画面的渲染工作要在 16ms 内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验。

  • 浏览器在一帧里面,会依次执行以上这些动作。减少或者避免 layout,paint 可以让页面不卡顿,动画效果更加流畅。

好,Performance可以了解浏览器在每一帧都干了什么。

Rendering

再来看看子选项卡Rendering

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第10张图片

主要用到:

  • Paint flashing:绿色高亮需要重绘的元素区域

  • Layer borders:橙色或橄榄色 显示 Layer 边框,青色 显示 板块

  • FPS meter:fps 画面每秒传输帧数,每秒钟帧数越多,所显示的动作就会越流畅。

    绘制 每秒帧数信息,帧速率分布 和 GPU 内存信息

先勾选Paint flashingLayer borders

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第11张图片

一片绿。

而且可以看到 b1b2形成了黄色的边框,即它们在动画过程中被合成了一层 Layer。

但是在动画结束之后,由于所有的图层没有指定z-index,最后一帧进行图层 Paint 的时候,出现了一些我们不希望看到的 绿色 的矩形。即浏览器在图层合成的时候又将它们合在了同一层 Repaint。

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第12张图片

再勾选FPS meter

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第13张图片

可以看到 fps 和 GPU Memory 的各项指标。

在全屏情况下,动画过程中可以达到 6.8 MB 的占用

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第14张图片

Layers

子选项卡Layers,可以看到页面的图层情况,这是个小 Demo ,所以不明显。大家可以拿自己的项目试试看

但是在动画过程中,确实可以看到b1b2 被迫形成一层图层,但是我们没做其他对于b的操作,这就是隐式合成a2在移动的过程中,由于b在其之上,a2是一个 Layer 了,但是b盖住a2,所以b也需要合成成一层 Layer。

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第15张图片

现尝试给 css 做优化

#a {
    ...
    z-index: 2;
}
#b {
    ...
    z-index: 1;
}

我们将a整体放在b之上,b不再盖住a2

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第16张图片

好多了。

并且在滑动过程中只占用了 4.7 MB 的GPU 内存。

层叠上下文初识 - 利用 chrome devTools 实现 css性能优化_第17张图片

Demo 4

实战中,在一个有 banner 的项目里,其中banner是存在动画效果的。

打开Rendering观察图层绘制情况:

banner滑动时,下面的任务栏也重绘了,这不是我们想要的,并且GPU Memory使用达到了 16.8MB

我们可以尝试将banner的层级提高,或者进行一些额外的优化

隐式合成/层压缩/层爆炸

注意,利用Layer 合成层优化也不是万能的,处理不得当,又会浪费了 GPU 资源。

隐式合成:

上文也有提及,由于两个层之间有交叠的部分,底下的合成层被上层遮挡,导致上层 Layer 也被合成为合成层。

层压缩:

如果重叠的元素过多,可能简单的重叠就会产生大量的合成层,这样会占用很多无辜的 CPU 和 内存资源,严重影响了页面的性能。这一点浏览器也考虑到了,因此就有了层压缩(Layer Squashing)的处理。

浏览器的自动的层压缩也不是万能的,有很多特定情况下,浏览器是无法进行层压缩的。

具体情况,可见:详谈层合成

层爆炸:

通过之前的介绍,我们知道同合成层重叠也会使元素提升为合成层,虽然有浏览器的层压缩机制,但是也有很多无法进行压缩的情况。也就是说除了我们显式的声明的合成层,还可能由于重叠原因不经意间产生一些不在预期的合成层,极端一点可能会产生大量的额外合成层,出现层爆炸的现象。

解决层爆炸的问题,最佳方案是打破 overlap 的条件,也就是说让其他元素不要和合成层元素重叠,譬如巧妙的使用 z-index属性。

而此段就正好解释了 Demo 3 z-index的效果。

所以,css 优化不是一蹴而就的,是要一点点不断摸索,才能找出更优的方案。

总结

通过此文,我们可以了解到:

  • 层叠上下文的概念,形成条件
  • Chrome devTools 中 RenderingLayersPerformance的使用
  • css 性能优化与层叠上下文、合成层的关系

你可能感兴趣的:(css)