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
的元素。fixed
或 sticky
的元素(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: strict
,contain: content
)。(**这是一项实验性技术,**请在生产中使用它之前仔细检查浏览器兼容性表。)综上:
层叠上下文可以被其他层叠上下文包含,并一起创建层叠上下文的层次结构
每个层叠上下文和其兄弟独立开来;处理层叠上下文时仅考虑后代元素
每个层叠上下文都是独立的,将元素的内容堆叠之后,将按照父层叠上下文的堆叠顺序考虑整个元素;
**注意:**比较堆叠顺序时,将只比较同级 DOM 的堆叠顺序,相同情况下再考虑子元素,直到一方比另一方大。
别被吓到了,总结下来就一句话:谁大谁上,后来居上
考虑如下两个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;
}
a1
和a2
在同一级即均为a
的子元素,可以看到a1
和a2
均设置了position: absolute
属性,故均可设置z-index
属性设置在a
这个层叠上下文中的层叠顺序。
根据谁大谁上的原则,a1
的z-index
比a2
大,故a2
在 Z 轴中在 a1
之下。
修改之:
#a1 {
...
/* z-index: 1; */
}
#a2 {
...
/* z-index: 0; */
}
现在均没了z-index
属性,效果如下:
根据后来居上的原则,由于都没有指定z-index
,故a2
后来居上,在 Z 轴中比a1
更高。
考虑如下场景
<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;
}
相较于 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
,由于 b
比 a
的 z-index
权值小,b
整体都比a
在 Z 轴上低,b
与 a
比对完之后,才是b
内部和 a
内部元素的比对。总之,在 Z 轴方向上:a1
> a2
> b1
> b2
在浏览器渲染过程中,每个 DOM 节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,即 Z 轴上位于同一平面,就会形成一个 RenderLayers ,也就是渲染层,此文简称 Layer。
Layer 将同一坐标空间的 LayoutObject 组织起来,保证页面元素以正确的顺序合成,这时候就会出现层合成(composite)。
某些特殊的渲染层会被认为是 合成层。合成层拥有单独的处理单元(GraphicsLayer),而其他不是合成层的渲染层 Layer,则和第一个拥有独立处理单元的父层共用一个处理单元。
那么上文的层叠上下文除了能帮助我们正确了解和处理元素在 Z 轴上的展示意外,当我们对层叠上下文进行优化处理时,可以得到 合成层 的 css 性能优化。
但是 层叠上下文 与 Layer 之间的关系可以 笼统地概括为:有自己的 layer 的元素则必定是一个层叠上下文,而是层叠上下文的元素不一定有自己的 layer。
工欲善其身,必先利其器。在尝试对层叠上下文进行层优化之前,必须要先熟悉一下 chrome 神器中的开发者工具。除了常用的 Element
和 Console
以外, 针对这次博客,我们可以在More tools
中找到 Layers
,Rendering
和Performance
工具栏。
在使用这三个工具时,我们需要结合 Demo 3
在 Demo 2的基础上给 a2
添加了动画,并且,将所有的z-index
属性去除
#a2 {
...
transition: all 2s linear;
transform: translate(100px,100px);
}
乍一看好像没什么,就是一个div
的平移,但是这背后浏览器做了一系列的操作。
我们可以打开 chrome devTools 的 Performance
面板:
可以点击实心圆进行录制,期间可以多次刷新浏览器;录制结束:
可以在 Event Log
子选项卡里看到:
Schedule Style Recalculation
-> Recalculate Style
-> Update Layer Tree
-> Paint
-> Composite Layers
基本是这么一个事件循环。
其实基本上,浏览器在每一帧里,会依次执行如下动作:
首先我们可以了解什么是 16ms 优化
大多数设备的刷新频率是 60 次/秒,(1000/60 = 16.6ms)也就说是浏览器对每一帧画面的渲染工作要在 16ms 内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验。
浏览器在一帧里面,会依次执行以上这些动作。减少或者避免 layout,paint 可以让页面不卡顿,动画效果更加流畅。
好,Performance
可以了解浏览器在每一帧都干了什么。
再来看看子选项卡Rendering
:
主要用到:
Paint flashing
:绿色高亮需要重绘的元素区域
Layer borders
:橙色或橄榄色 显示 Layer 边框,青色 显示 板块
FPS meter
:fps 画面每秒传输帧数,每秒钟帧数越多,所显示的动作就会越流畅。
绘制 每秒帧数信息,帧速率分布 和 GPU 内存信息
先勾选Paint flashing
,Layer borders
:
一片绿。
而且可以看到 b1
和b2
形成了黄色的边框,即它们在动画过程中被合成了一层 Layer。
但是在动画结束之后,由于所有的图层没有指定z-index
,最后一帧进行图层 Paint 的时候,出现了一些我们不希望看到的 绿色 的矩形。即浏览器在图层合成的时候又将它们合在了同一层 Repaint。
再勾选FPS meter
:
可以看到 fps 和 GPU Memory 的各项指标。
在全屏情况下,动画过程中可以达到 6.8 MB 的占用
子选项卡Layers
,可以看到页面的图层情况,这是个小 Demo ,所以不明显。大家可以拿自己的项目试试看
但是在动画过程中,确实可以看到b1
和b2
被迫形成一层图层,但是我们没做其他对于b
的操作,这就是隐式合成。a2
在移动的过程中,由于b
在其之上,a2
是一个 Layer 了,但是b
盖住a2
,所以b
也需要合成成一层 Layer。
现尝试给 css 做优化
#a {
...
z-index: 2;
}
#b {
...
z-index: 1;
}
我们将a
整体放在b
之上,b
不再盖住a2
。
好多了。
并且在滑动过程中只占用了 4.7 MB 的GPU 内存。
实战中,在一个有 banner
的项目里,其中banner
是存在动画效果的。
打开Rendering
观察图层绘制情况:
在banner
滑动时,下面的任务栏也重绘了,这不是我们想要的,并且GPU Memory
使用达到了 16.8MB
我们可以尝试将banner
的层级提高,或者进行一些额外的优化
注意,利用Layer 合成层优化也不是万能的,处理不得当,又会浪费了 GPU 资源。
上文也有提及,由于两个层之间有交叠的部分,底下的合成层被上层遮挡,导致上层 Layer 也被合成为合成层。
如果重叠的元素过多,可能简单的重叠就会产生大量的合成层,这样会占用很多无辜的 CPU 和 内存资源,严重影响了页面的性能。这一点浏览器也考虑到了,因此就有了层压缩(Layer Squashing)的处理。
浏览器的自动的层压缩也不是万能的,有很多特定情况下,浏览器是无法进行层压缩的。
具体情况,可见:详谈层合成
通过之前的介绍,我们知道同合成层重叠也会使元素提升为合成层,虽然有浏览器的层压缩机制,但是也有很多无法进行压缩的情况。也就是说除了我们显式的声明的合成层,还可能由于重叠原因不经意间产生一些不在预期的合成层,极端一点可能会产生大量的额外合成层,出现层爆炸的现象。
解决层爆炸的问题,最佳方案是打破 overlap
的条件,也就是说让其他元素不要和合成层元素重叠,譬如巧妙的使用 z-index
属性。
而此段就正好解释了 Demo 3 z-index
的效果。
所以,css 优化不是一蹴而就的,是要一点点不断摸索,才能找出更优的方案。
通过此文,我们可以了解到:
Rendering
,Layers
,Performance
的使用