skip to content
OnionTalk

层叠上下文和层叠顺序

在日常开发中,我们经常会遇到元素需要被遮盖的场景。在这种场景下绝大多数前端开发的第一反应是使用z-index来设定元素的层叠关系,但是在一些情况下z-index并不是会如果我们预期那样正常工作。究其背后的原因,就不得不引入我们今天要聊的层叠上下文(stacking context)。

为什么 z-index 不工作了?

在某些场景下,我们经常会发现 z-index 不知为何就不工作了,比如下面的场景。(demo:stackblitz demo)

<div class="block-a">block-a z-index 1</div>
<div class="block-b">block-b z-index 2</div>
<div class="block-c">block-c z-index 3</div>
.block-a {
  background: #ffb3a7;
  z-index: 1;
  position: relative;
  top: 60px;
  opacity: 0.8;
  width: 500px;
  height: 90px;
}

.block-b {
  background: #fff143;
  z-index: 2;
  opacity: 0.8;
  height: 100px;
  width: 320px;
  position: fixed;
}

.block-c {
  background: #bddd22;
  opacity: 0.8;
  z-index: 3;
  width: 200px;
  padding-top: 300px;
}

div {
  color: #50616d;
  font-size: 24px;
  text-align: center;
  padding-top: 40px;
  border: #50616d 1px dashed;
}

block-a 的 z-index 是 1, block-b 的 z-index 是 2, block-c 的 z-index 是 3。 按照之前的理解,覆盖关系应该是 block-c 覆盖 block-b 覆盖 block-a。但是实际结果却如下图。

为什么会这样呢?其实产生这个现象的原因就和我们今天要说的层叠上下文有关。

什么是层叠上下文

层叠上下文其实是一个网页元素在三维空间中的概念,是元素在 z 轴上的一个相对位置的描述。如果用过 Photoshop,大家一定会接触过图层的概念,层叠上下文其实和图层的概念非常相似。也就是说,最终展现在用户眼前的网页,并不简单只是一个二维的平面图形(即使绝大多数时候我们都这么认为),而是一个个层叠上下文互相作用的结果。

层叠上下文是怎么作用的?

没有 z-index,视图之间存在层叠吗?

答案是,即使没有任何元素设置 z-index,视图之间依然存在层叠关系。

我们都知道,使用 z-index 可以在一些情况下(具体特殊场景下一节会详细说明)可以来控制视图的层叠关系。然而在没有任何元素设置`z-index`时,元素之间仍存在着一定的层叠规则。规则根据官方规范如下:

  1. 层叠上下文的背景和 border 位于最底层
  2. positioned 的元素位于根元素上面
  3. positioned 元素位于非 positioned 元素之上
<div id="abs1" class="absolute"><b>DIV #1</b><br />position: absolute;</div>
<div id="rel1" class="relative"><b>DIV #2</b><br />position: relative;</div>
<div id="rel2" class="relative"><b>DIV #3</b><br />position: relative;</div>
<div id="abs2" class="absolute"><b>DIV #4</b><br />position: absolute;</div>
<div id="sta1" class="static"><b>DIV #5</b><br />position: static;</div>
.absolute {
  position: absolute;
  background: #fff143;
  width: 150px;
  height: 350px;
  opacity: 0.8;
}

#abs1 {
  top: 10px;
  left: 10px;
}

#rel1 {
  top: 30px;
  margin: 0px 50px 0px 50px;
}

#rel2 {
  top: 15px;
  left: 20px;
  margin: 0px 50px 0px 50px;
}

#abs2 {
  top: 10px;
  right: 10px;
}

#sta1 {
  margin: 0px 50px 0px 50px;
}

.relative {
  position: relative;
  background: #bddd22;
  height: 80px;
  opacity: 0.8;
}

.static {
  background: #ffb3a7;
  position: static;
}

div {
  color: #50616d;
  font-size: 24px;
  text-align: center;
  border: #50616d 1px dashed;
}

demo

使用 z-index 时层叠上下文时的层叠规则

使用 z-index 时,在同一层叠上下文中,所有的元素按照 z-index 的大小由底向上层叠。需要注意的是,这里强调是在同一层叠上下文中设置 z-index 才会生效,在不同的层叠上下文中设置 z-index 并不会如预期那样工作。

 {
  margin: 0;
}
html {
  padding: 20px;
  font: 12px/20px Arial, sans-serif;
}
div {
  opacity: 0.7;
  position: relative;
}
h1 {
  font: inherit;
  font-weight: bold;
}
#div1,
#div2 {
  border: 1px dashed #696;
  padding: 10px;
  background-color: #bddd22;
}
#div1 {
  z-index: 5;
  margin-bottom: 190px;
}
#div2 {
  z-index: 2;
}
#div3 {
  z-index: 4;
  opacity: 1;
  position: absolute;
  top: 40px;
  left: 180px;
  width: 330px;
  border: 1px dashed #900;
  background-color: #ffb3a7;
  padding: 40px 20px 20px;
}
#div4,
#div5 {
  border: 1px dashed #996;
  background-color: #fff143;
}
#div4 {
  z-index: 6;
  margin-bottom: 15px;
  padding: 25px 10px 5px;
}
#div5 {
  z-index: 1;
  margin-top: 15px;
  padding: 5px 10px;
}
#div6 {
  z-index: 3;
  position: absolute;
  top: 20px;
  left: 180px;
  width: 150px;
  height: 125px;
  border: 1px dashed #009;
  padding-top: 125px;
  background-color: #ddf;
  text-align: center;
}
<div id="div1">
  <h1>Division Element #1</h1>
  <code
    >position: relative;<br />
    z-index: 5;</code
  >
</div>

<div id="div2">
  <h1>Division Element #2</h1>
  <code
    >position: relative;<br />
    z-index: 2;</code
  >
</div>

<div id="div3">
  <div id="div4">
    <h1>Division Element #4</h1>
    <code
      >position: relative;<br />
      z-index: 6;</code
    >
  </div>

  <h1>Division Element #3</h1>
  <code
    >position: absolute;<br />
    z-index: 4;</code
  >

  <div id="div5">
    <h1>Division Element #5</h1>
    <code
      >position: relative;<br />
      z-index: 1;</code
    >
  </div>

  <div id="div6">
    <h1>Division Element #6</h1>
    <code
      >position: absolute;<br />
      z-index: 3;</code
    >
  </div>
</div>

Root

  • DIV #1
  • DIV #2
  • DIV #3
    • DIV #4
    • DIV #5
    • DIV #6

DIV1,DIV2 和 DIV3 都位于 Root 层叠上下文之下,所以其层叠会根据 z-index 进行排列。

而对于 DIV4,虽然其`z-index` 是 6, 但是其位于 DIV3 的层叠上下文中,然而 DIV3 的 z-index 为 4,所以 DIV4 会被覆盖。这里也可以引入一个简单的计算公式来比较好的表达元素的层叠关系。

  • DIV1 = Root.z-index | DIV1.z-index = 0 | 5
  • DIV2 = Root.z-index | DIV2.z-index = 0 | 2
  • DIV3 = Root.z-index | DIV3.z-index = 0 | 3
  • DIV4 = Root.z-index | DIV3.z-index | DIV4.z-index = 0 | 3 | 6
  • DIV5 = Root.z-index | DIV3.z-index | DIV5.z-index = 0 | 3 | 1
  • DIV6 = Root.z-index | DIV3.z-index | DIV6.z-index = 0 | 3 | 3

从左往右依次比较便可得到元素的层级关系。

浮动元素的层叠规则

浮动元素的层叠顺序和非浮动元素有所不同,按照下面这个顺序进行层叠。

  1. 层叠上下文的背景和 border 位于最底层
  2. 非`positioned` 的块级元素位于根元素上面
  3. 浮动元素位于非 positioned 的块级元素上面
  4. 非 positioned 的非块级元素位于浮动元素上面
  5. `positioned` 元素位于非`positioned` 元素之上

demo

div {
  padding: 10px;
  text-align: center;
}

b {
  font-family: sans-serif;
}

#abs1 {
  position: absolute;
  width: 150px;
  height: 200px;
  top: 10px;
  right: 140px;
  border: 1pxdashed#900;
  background-color: #ffb3a7;
}

#sta1 {
  height: 100px;
  border: 1pxdashed#996;
  background-color: #fff143;
  margin: 0px10px0px10px;
  text-align: left;
}

#flo1 {
  margin: 0px10px0px20px;
  float: left;
  width: 150px;
  height: 200px;
  border: 1pxdashed#090;
  background-color: #bddd22;
}

#flo2 {
  margin: 0px20px0px10px;
  float: right;
  width: 150px;
  height: 200px;
  border: 1pxdashed#090;
  background-color: #bddd22;
}

#abs2 {
  position: absolute;
  width: 150px;
  height: 100px;
  top: 130px;
  left: 100px;
  border: 1pxdashed#990;
  background-color: #ffb3a7;
}
<div id="abs1"><b>DIV#1</b><br />position:absolute;</div>

<div id="flo1"><b>DIV#2</b><br />float:left;</div>

<div id="flo2"><b>DIV#3</b><br />float:right;</div>

<br />

<div id="sta1"><b>DIV#4</b><br />nopositioning</div>

<div id="abs2"><b>DIV#5</b><br />position:absolute;</div>

什么时候会产生层叠上下文

根据MDN文档,以下这些比较常见的情况下会产生层叠上下文(在这里省略了不太常见的一些场景,需要的同学可以自行阅读文档)

  1. 根标签 `` 标签会产生一个层叠上下文。
  2. positionabsolute 或者 relative 并且 z-index 不为 auto 的元素。
  3. positionfixed 或者 sticky 的元素。
  4. 所有 flex container 的 z-index 不为 auto 的子元素。
  5. 所有 gridz-index 不为 auto 的子元素
  6. 所有 opacity 小于 1 的元素。
  7. 在指定了以下 css 属性且属性值不为 none
    • transform
    • filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border

回顾我们之前的例子

在最开始的例子中,我们发现我们的元素并没有按照我们设定的 z-index 进行层叠。经过这一系列的说明,这其中的原因就很容易理解了。block-b 的 z-index 虽然为 2,但是因为他已经是一个 positioned 的元素,所以这个元素已经处于更上层的层叠上下文之中了。对于不在同一层叠上下文中的元素,不管你怎么去设置 z-index ,都是不会起作用的。

总结

  1. z-index 并不是总是起作用。
  2. 网页是多个层叠上下文互相作用的结果。
  3. 当没有 z-index 时,元素之间有默认的层叠关系,根元素位于最下,非 positioned 元素层叠在根元素之上,positioned 元素层叠在非 positioned 元素之上。
  4. 当有 z-index 时,处于同一层叠上下文中的元素按照 z-index 进行层叠。
  5. 浮动元素有其特殊的层叠规则。

参考资料

the stacking context

stacking without z-index

stacking with float blocks

using z-index