堆叠上下文
堆叠上下文,英语全称 stacking context,经常也被翻译为“层叠上下文”,一说到层叠上下文,很多同学自然而然就会想到 z-index。但是大家在使用 z-index 时,往往对该属性的认识比较片面,认为 z-index 就是用来描述定义一个元素在屏幕 Z 轴上的堆叠顺序。z-index 值越大在 Z 轴上就越靠上,也就是离屏幕观察者越近。
但是其实这个认识存在很大的问题:
- 首先,z-index 属性值并不是在任何元素上都有效果。它仅在定位元素(定义了 position 属性,且属性值为非 static 值的元素)上有效果。
- 判断元素在 Z 轴上的堆叠顺序,不仅仅是直接比较两个元素的 z-index 值的大小,这个堆叠顺序实际由元素的层叠上下文、层叠等级共同决定。
接下来我们需要先来了解几个概念,层叠上下文、层叠等级、层叠顺序都是什么?
什么是层叠上下文
层叠上下文(stacking context),是 HTML 中一个三维的概念。
在 CSS2.1 规范中,每个盒模型的位置是三维的,分别是平面画布上的 X 轴,Y 轴以及表示层叠的 Z 轴。
一般情况下,元素在页面上沿 X 轴 Y 轴平铺,我们察觉不到它们在 Z 轴上的层叠关系。而一旦元素发生堆叠,这时就能发现某个元素可能覆盖了另一个元素或者被另一个元素覆盖。
如果一个元素含有层叠上下文,(也就是说它是层叠上下文元素),我们可以理解为这个元素在 Z 轴上就“高人一等”,最终表现就是它离屏幕观察者更近。
举一个比较形象一点的例子,你可以把层叠上下文元素理解为理解为 该元素当了官,而其他非层叠上下文元素则可以理解为普通群众。凡是“当了官的元素”就比普通元素等级要高,也就是说元素在 Z 轴上更靠上,更靠近观察者。
什么是层叠等级
那么,层叠等级指的又是什么?
层叠等级英文全称 stacking level,也有翻译成“层叠级别”或者“层叠水平”。
在同一个层叠上下文中,它描述定义的是该层叠上下文中的层叠上下文元素在 Z 轴上的上下顺序。在其他普通元素中,它描述定义的是这些普通元素在 Z 轴上的上下顺序。
说到这,可能很多人疑问了,不论在层叠上下文中还是在普通元素中,层叠等级都表示元素在 Z 轴上的上下顺序,那就直接说它描述定义了所有元素在 Z 轴上的上下顺序就 OK 啊!为什么要分开描述?
我们还是以上面当官的例子来说明:上面说到,处于层叠上下文中的元素,就像是元素当了官,等级自然比普通元素高。
再想象一下,假设一个官员 A 是个省级领导,他下属有一个秘书 a-1,家里有一个保姆 a-2。
另一个官员 B 是一个县级领导,他下属有一个秘书 b-1,家里有一个保姆 b-2。
a-1 和 b-1 虽然都是秘书,但是你想一个省级领导的秘书和一个县级领导的秘书之间有可比性么?
甚至保姆 a-2 都要比秘书 b-1 的等级高得多。谁大谁小,谁高谁低一目了然,所以根本没有比较的意义。
只有在 A 下属的 a-1、a-2 以及 B 下属的 b-1、b-2 中相互比较大小高低才有意义。
再类比回层叠上下文和层叠等级,就得出一个结论:
- 普通元素的层叠等级优先由其所在的层叠上下文决定。
- 层叠等级的比较只有在当前层叠上下文元素中才有意义。不同层叠上下文中比较层叠等级是没有意义的。
如何产生层叠上下文
前面说了那么多,知道了“层叠上下文”和“层叠等级”,其中还有一个最关键的问题:到底如何产生层叠上下文呢?
如何让一个元素变成层叠上下文元素呢 ?
其实,层叠上下文也基本上是有一些特定的 CSS 属性创建的,一般有 3 种方法:
- HTML 中的根元素 HTML 本身就具有层叠上下文,称为“根层叠上下文”。
- 普通元素设置 position 属性为非 static 值并设置 z-index 属性为具体数值,产生层叠上下文
- CSS3 中的新属性也可以产生层叠上下文
我们来看两个具体的例子:
<div>
<p class="a">a</p>
<p class="b">b</p>
</div>
<div>
<p class="c">c</p>
</div>
div {
position: relative;
width: 100px;
height: 100px;
}
p {
position: absolute;
font-size: 20px;
width: 100px;
height: 100px;
}
.a {
background-color: blue;
z-index: 1;
}
.b {
background-color: green;
z-index: 2;
top: 20px;
left: 20px;
}
.c {
background-color: red;
z-index: 3;
top: -20px;
left: 40px;
}
效果如下,因为 p.a、p.b、p.c 三个的父元素 div 都没有设置 z-index,所以不会产生层叠上下文,所以 .a、.b、.c 都处于由 html 标签产生的“根层叠上下文”中,属于同一个层叠上下文,此时谁的 z-index 值大,谁在上面。
<div class="box1">
<p class="a">a</p>
<p class="b">b</p>
</div>
<div class="box2">
<p class="c">c</p>
</div>
div {
width: 100px;
height: 100px;
position: relative;
}
.box1 {
z-index: 2;
}
.box2 {
z-index: 1;
}
p {
position: absolute;
font-size: 20px;
width: 100px;
height: 100px;
}
.a {
background-color: blue;
z-index: 100;
}
.b {
background-color: green;
top: 20px;
left: 20px;
z-index: 200;
}
.c {
background-color: red;
top: -20px;
left: 40px;
z-index: 9999;
}
效果:
在上面的示例中,我们发下,虽然 p.c 元素的 z-index 值为 9999,远大于 p.a 和 p.b 的 z-index 值,但是由于 p.a、p.b 的父元素 div.box1 产生的层叠上下文的 z-index 的值为 2,p.c 的父元素 div.box2 所产生的层叠上下文的 z-index 值为 1,所以 p.c 永远在 p.a 和 p.b 下面。
同时,如果我们只更 p.a 和 p.b 的 z-index 值,由于这两个元素都在父元素 div.box1 产生的层叠上下文中,所以,谁的 z-index 值大,谁在上面。
什么是层叠顺序
说完“层叠上下文”和“层叠等级”,我们再来说说“层叠顺序”。
“层叠顺序”(stacking order)表示元素发生层叠时按照特定的顺序规则在 Z 轴上垂直显示。
说简单一点就是当元素处于同一层叠上下文内时如何进行层叠判断。
这里我们就可以来总结一下层叠等级的判断规则:
首先先看要比较的两个元素是否处于同一个层叠上下文中:
- 如果是,谁的层叠等级大,谁在上面(判断层叠等级大小参阅上面的“层叠顺序”图)
- 如果两个元素不在统一层叠上下文中,请先比较他们所处的层叠上下文的层叠等级。
- 当两个元素层叠等级相同、层叠顺序相同时,在 DOM 结构中后面的元素层叠等级在前面元素之上。
接下来我们还是来看两个示例,如下:
<div class="box1">
<div class="child1"></div>
</div>
<div class="box2">
<div class="child2"></div>
</div>
.box1,
.box2 {
position: relative;
z-index: auto;
}
.child1 {
width: 200px;
height: 100px;
background: #168bf5;
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
.child2 {
width: 100px;
height: 200px;
background: #32c292;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
效果:
在上面的示例中, .box1/.box2 虽然设置了 position: relative,但是 z-index: auto 的情况下,这两个 div 还是普通元素,并没有产生层叠上下文。所以,child1/.child2 属于 html 元素的“根层叠上下文”中,此时,谁的 z-index 值大,谁在上面。
将上面案例中的 CSS 代码稍作修改,如下:
.box1,
.box2 {
position: relative;
z-index: 0;
}
效果:
此时,我们发现,仅仅修改了 .box1/.box2 的 z-index 属性值改为数值 0,最终结果完全相反。
这时 .child2 覆盖在了 .child1 上面。原因是什么呢?
很简单:因为设置 z-index: 0 后, .box1/.box2 产生了各自的层叠上下文,这时候要比较 .child1/.child2 的层叠关系完全由父元素 .box1/.box2 的层叠关系决定。
但是 .box1/.box2 的 z-index 值都为 0,都是块级元素(所以它们的层叠等级,层叠顺序是相同的),这种情况下,在 DOM 结构中后面的覆盖前面的,所以 .child2 就在上面。
CSS3 中的属性对层叠上下文的影响
CSS3 中出现了很多新属性,其中一些属性对层叠上下文也产生了很大的影响。如下:
- 父元素的 display 属性值为 flex|inline-flex,子元素 z-index 属性值不为 auto 的时候,子元素为层叠上下文元素
- 元素的 opacity 属性值不是 1
- 元素的 transform 属性值不是 none
- 元素 mix-blend-mode 属性值不是 normal
- 元素的 filter 属性值不是 none
- 元素的 isolation 属性值是 isolate
- will-change 指定的属性值为上面任意一个
- 元素的 -webkit-overflow-scrolling 属性值设置为 touch
CSS3 中,元素属性满足以上条件之一,就会产生层叠上下文。我们用第 1 条来做一个简单的解释说明。
<div class="box">
<div class="parent">
parent
<div class="child">child</div>
</div>
</div>
.box {
}
.parent {
width: 200px;
height: 100px;
background: #168bf5;
/* 虽然设置了z-index,但是没有设置position,z-index无效,.parent还是普通元素,没有产生层叠上下文 */
z-index: 1;
}
.child {
width: 100px;
height: 200px;
background: #32d19c;
position: relative;
z-index: -1;
}
效果:
我们发现, .child 被 .parent 覆盖了。按照之前的规则来分析一下:
虽然 .parent 设置了 z-index 属性值,但是没有设置 position 属性,z-index 无效,所以没有产生层叠上下文, .parent 还是普通的块级元素。此时,在层叠顺序规则中,z-index 值小于 0 的 .child 会被普通的 block 块级元素 .parent 覆盖。
对于上面的栗子,我们只修改 .box 的属性,设置 display: flex,其余属性和 DOM 结构不变。
.box {
display: flex;
}
效果:
在上面的示例中,当给 .box 设置 display: flex 时, .parent 就变成层叠上下文元素。
根据层叠顺序规则,层叠上下文元素的 background/border 的层叠等级小于 z-index 值小于 0 的元素的层叠等级,所以 z-index 值为 -1 的.child 在 .parent 上面。
最后我们来看一个综合性的层叠上下文的练习题:
<div class="parent">
parent
<div class="child1">child1</div>
<div class="child2">
child2
<div class="child2-1">child2-1</div>
<div class="child2-2">child2-2</div>
</div>
</div>
.parent {
width: 100px;
height: 200px;
background: #168bf5;
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
.child1 {
width: 100px;
height: 200px;
background: #32d19c;
position: absolute;
top: 20px;
left: 20px;
z-index: 1;
}
.child2 {
width: 100px;
height: 200px;
background: #e4c950;
position: absolute;
top: 40px;
left: 40px;
z-index: -1;
}
.child2-1 {
width: 100px;
height: 200px;
background: #e45050;
position: absolute;
top: 60px;
left: 60px;
z-index: 9999;
}
.child2-2 {
width: 100px;
height: 200px;
background: #db68a7;
position: absolute;
top: 80px;
left: 40px;
z-index: -9999;
}
上面的各个 div 是如何进行层叠的呢?
效果如下,大家可以自己分析一波。