前言
在我们实际开发过程中,时常会遇到层级问题,但是当使用 z-index 时效果又不如预期,本质上还是对 层叠上下文 的了解不够深入导致,以下将从几个角度重新理解层叠上下文
层叠上下文
层叠上下文(stacking context),是HTML中一个三维的概念。简单的说层叠上下文类似于 Photoshop 中的 组,而元素则对应组中的图层以此来体现层叠关系。
层叠等级
- 在层叠上下文中,表示的是层叠上下文元素在
Z轴上的堆叠顺序 - 在其他元素中,表示普通元素在
Z轴上的堆叠顺序
那么这里就产生了一个疑惑,那么既然层叠等级指层叠顺序,那为什么z-index的设置会不如预期,如图.tooltip 的 z-index 显然大于 header,但结果不如预期
为什么
因为层叠等级的比较需要在相同的层叠上下文中比较才有意义,不同层叠上下文中比较层叠等级是没有意义的,同理与 Photoshop 相同的组别内的图层堆叠等级比较才有意义,那么我们将上图的结构进行解析可以得到:
可以很明显的看出.tooltip 的 9999 仅仅是在 main 这个层叠上下文中进行比较
修复示例
我们仅仅需要将 .main 中的 z-index 去掉即可
去掉之后我们的层叠上下文如下所示
我们的tooltip 与 header处在了同一上下文中进行比较,那么为什么去掉 main 的 z-index 之后他的层叠上下文就消失了呢?这要涉及到层叠上下文的创建规则
创建层叠上下文
-
HTML中的根元素<html></html>本身j就具有层叠上下文,称为“根层叠上下文” -
position属性为relative或absolute(并设置z-index属性) -
position属性为fixed或sticky(无需 z-index 直接产生上下文) -
opacity小于1 -
mix-blend-mode不为normal -
display: flex或display: grid(并给子元素设置z-index属性) -
使用
transform,filter,clip-path, 或perspective -
will-change值为opacity或transform -
显性地创建层叠上下文
isolation: isolate完整列表:MDN
常见误解
第二个盒子显然通过z-index堆叠与其他兄弟节点之前,但是却没有设置 position 相关属性,这是由于在 Flexbox 规则中添加了一个例外: flex 子元素即使 position 为 static 依然可以使用 z-index
因此说所有元素都可以使用 z-index 是一个误区,Flexbox 是一个特例
稍等一下......
这里有一些奇怪的事情值得我们思考一两分钟
在 Photoshop 中,组与层之间是有明显差异的,可视元素为层,而组则在结构上包裹层,这是一种很清晰的概念;但在web中,这种差异就显得模糊了,任何想要使用z-index的元素都需要创建层叠上下文;
而当我们决定给元素添加一个z-index时,我们的目的又变成在 该元素的父级层叠上下文中降低或提升该元素的层叠等级。
当我们为此创建层叠上下文时,那么该元素就变成了一个组,原先该组的子元素层叠等级被扁平化包裹在组内,同时也将子元素牢牢锁在这个组中
我们重新看回示例代码:
<header>
不你不能
</header>
<main>
<div class="tooltip">
我想站前面
</div>
<p>Some main content</p>
</main>
在默认情况下,HTML 元素会根据DOM的顺序进行堆叠,也就是说我们并不需要任何的 CSS 介入也可以让 main 现实在 header 之前,我们随后通过z-index改变了header的堆叠顺序,但于此同时header的子元素也全部被扁平化。
我们不能单纯的将z-index理解为改变层叠顺序的一种方式,同时也应该将z-index理解为将其子元素组成小组的一种方式
那或许你会说为什么不能讲z-index进行全局比较模糊组的概念,试想一下如果开发过程中每个元素都要全局比较那10000个元素又怎么比较?
同时也能利用这一特性来“封闭”元素,对于像
React这种组件驱动的框架及其有用
isolation 抽象概念
这是一个非常有用的 CSS 属性,同时也是最令人感到疑惑的属性质疑,这或许是一个 “宝藏”属性
如何使用:
.wrapper {
isolation: isolate;
}
当使用这个属性时,他只做一件事情:创建一个层叠上下文
那么已经有了上述这么多创建层叠上下文的方法,为什么我们又需要这一个呢?
反观其他创建方式,你会发现他们都是通过一些其他修改来隐式创建的,而 isolation 则是显性创建:
- 不需要规定
z-index的值 - 可以作用到
static元素 - 不会影响到子元素
示例
有这样一个需求:当鼠标移入展开信封
结构如下:
示例代码如下:
function Envelope({ children }) {
return (
<div>
<BackPane style={{ zIndex: 1 }} />
<Letter style={{ zIndex: 3 }}>
{children}
</Letter>
<Shell style={{ zIndex: 4 }} />
<Flap style={{ zIndex: isOpen ? 2 : 5 }} />
</div>
)
}
乍一看似乎没有问题,但当我们添加一个header并设置z-index:3,问题出现了,虽然四个组件包裹在一个div中但并没有创建层叠上下文,导致z-index开始于外部进行比较,这些层开始交织在一起
我们通过使用 isolation: isolate 进行隔离,确保该组件为一组:
function Envelope({ children }) {
return (
<div style={{ isolation: 'isolate' }}>
<BackPane style={{ zIndex: 1 }} />
<Letter style={{ zIndex: 3 }}>
{children}
</Letter>
<Shell style={{ zIndex: 4 }} />
<Flap style={{ zIndex: isOpen ? 2 : 5 }} />
</div>
)
}
那么为什么不使用 position: relative; z-index: 1 来解决这个问题?因为 React 组件意味着可复用性,那么 这里的 z-index: 1 永远正确并且适用于其他页面吗?显然不是,这种隔离的概念巧妙地增加了组件的灵活性。
开发调试
目前在 Microsoft Edge 已经实现了 3D 视图功能,这个功能对层叠上下文调试非常有用
注意
Chrome 中的 layer 看起来很类似,但其实不然,图层跟层叠上下文不是一个概念,一个图层可以包含多个层叠上下文,这里的图层指的是 图形层,要讲图形层又涉及到 渲染层和合成层,感兴趣可以看这里
插件
-
在 Chrome 中调试可以使用插件 CSS Stacking Context inspector
- 通过VS code 插件在开发过程中及时发现问题 :VSCode extension that highlights when stacking contexts are created