为什么具有z-index值的元素不能覆盖其子元素?

50
今天经过数小时的调试后,我以一种艰难的方式学到了以下内容:

如果一个父元素具有任何值的z-index,那么它永远无法覆盖(堆叠在顶部)其子元素,无论如何更改子元素的CSS

我该如何理解这种行为?这是否符合规范?

.container {
  width: 600px;
  height: 600px;
  background-color: salmon;
  position: relative;
  z-index: 99;
  padding-top: 10px;
}

h1 {
  background-color: pink;
  position: relative;
  z-index: -1;
  font-family: monospace;
}
<div class="container">
  <h1>1. I can never be covered by parent if my z-index is positive.</h1>
  <h1>2. Even when my z-index is nagative, I still can never be covered if my parent has any z-index at all.</h1>
</div>

Negative z-index child above parent element


1
看一下这个链接,会是一篇有趣的阅读... - kukkuz
1
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context - kukkuz
https://philipwalton.com/articles/what-no-one-told-you-about-z-index/ - 04FS
1
@HarisGhauri 接受的答案中有你需要的解释 ;) - Temani Afif
1
@LoiNguyenHuynh 是的,没错。主要的技巧是避免子元素被困在父元素创建的堆叠上下文中。这样,两者(父元素和子元素)将属于同一个堆叠上下文(一个更高级别的DOM元素),并且它们将像兄弟元素一样运作(你可以在两者之间有任何绘画顺序)。 - Temani Afif
显示剩余3条评论
5个回答

73

要了解每个元素何时绘制,需要知道其堆叠上下文在该堆叠上下文中的层叠级别(由z-index定义)。还需要知道该元素是否建立堆叠上下文。设置z-index将会触发这一点:

对于定位框,z-index属性指定:

  1. 当前堆叠上下文中框的层叠顺序。
  2. 框是否建立新的堆叠上下文

取值的含义如下:

<integer>

生成的框在当前堆叠上下文中的层叠级别为此整数。该框还会建立新的堆叠上下文

有两件重要的事情需要知道:绘制顺序和堆叠上下文。如果参考规范,可以找到元素如何以及何时被绘制。

  1. 具有负z-index(不包括0)的定位后代形成的堆叠上下文以z-index顺序(最负的先)然后树顺序排列。

  1. 所有定位、不透明或变换后代,按树顺序分为以下类别:

    1. 所有具有'z-index: auto'或'z-index: 0'的定位后代,按树顺序。

  1. 具有大于或等于1的z-index的定位后代形成的堆叠上下文以z-index顺序(最小的先)然后树顺序排列。

从中可以清楚地看出,首先在第3步绘制带有负数z-index的元素,然后在第8步中绘制z-index等于0的元素,最后在第9步中绘制具有正数z-index的元素,这是合理的。我们还可以在规范的另一部分中读到:

每个框都属于一个堆叠上下文。给定堆叠上下文中的每个框都有一个整数堆叠级别,它是相对于同一堆叠上下文中的其他框在z轴上的位置。具有更高堆叠级别的框始终位于堆叠级别较低的框前面。框可以具有负堆叠级别。在堆叠上下文中具有相同堆叠级别的框根据文档树顺序自下而上堆叠。

auto

当前堆栈上下文中生成的框的堆栈级别为0。除非它是根元素,否则该框不会建立新的堆栈上下文


现在我们拥有所有信息来更好地理解每种情况。如果父元素具有与auto不同的z-index值,则它将创建一个堆叠上下文,因此子元素将被绘制在其z-index(负或正)内部。子元素的z-index将仅告诉我们以父元素为基础的绘制顺序(这涵盖了您的第二个点)

现在,如果只有子元素具有正z-index,并且我们在父元素上没有设置任何内容,则考虑绘制顺序,子元素将在步骤(9)后绘制,父元素将在步骤(8)中绘制。将父元素上移的唯一逻辑方法是增加z-index,但这样做将使我们陷入前面的情况,其中父元素将建立一个堆叠上下文,子元素将属于它。

当子元素设置正z-index时,没有办法使父元素位于其上方。如果我们在父元素上设置与auto不同的z-index(正或负),也没有办法使父元素位于子元素之上。1

唯一可以让子元素位于其父元素下方的情况是在子元素上设置负z-index并保持父元素为z-index: auto,这样就不会创建堆叠上下文,并按照绘制顺序先绘制子元素。


除了z-index,还有其他属性创建堆叠上下文。如果您遇到预期的堆叠顺序,则需要考虑这些属性,以查看是否创建了堆叠上下文。


从以上内容中我们可以得出一些重要的结论:

  1. 堆叠上下文可以包含在其他堆叠上下文中,并共同创建一个堆叠上下文层级。
  2. 每个堆叠上下文完全独立于其兄弟:只有后代元素在堆叠时才会被考虑。
  3. 每个堆叠上下文都是自包含的:在元素内容堆叠后,整个元素将被视为父堆叠上下文的堆叠顺序。 ref

1: 如果考虑使用3D变换,则存在某些hacky方式。

例如,一个元素会在其父元素下面,即使父元素已指定z-index。

.box {
  position:relative;
  z-index:0;
  height:80px;
  background:blue;
  transform-style: preserve-3d; /* This is important */
}
.box > div {
  margin:0 50px;
  height:100px;
  background:red; 
  z-index:-1; /* this will do nothing */
  transform:translateZ(-1px); /* this will do the magic */
}
<div class="box">
  <div></div>
</div>

另一个示例,我们可以在另一个堆叠上下文中的两个元素之间放置一个元素:

.box {
  position: relative;
  transform-style: preserve-3d;
  z-index: 0;
  height: 80px;
  background: blue;
}

.box>div {
  margin: 0 50px;
  height: 100px;
  background: red;
  z-index: 5;
  transform: translateZ(2px);
}

.outside {
  height: 50px;
  background: green;
  margin: -10px 40px;
  transform: translateZ(1px);
}

body {
  transform-style: preserve-3d;
}
<div class="box">
  <div></div>
</div>

<div class="outside"></div>

我们也可以像下面这样有一些疯狂的层叠顺序:

.box {
  width: 100px;
  height: 100px;
  position: absolute;
}

body {
  transform-style: preserve-3d;
}
<div class="box" style="top:100px;left:50px;background:red;"></div>
<div class="box" style="top: 50px;left: 115px;background:blue;"></div>
<div class="box" style="top: 101px;left: 170px;background:green;"></div>
<div class="box" style="top: 175px;left: 115px;background:purple;transform: rotateY(-1deg);"></div>

CSS圆形叠加上下文

需要注意的是,使用这种技巧可能会产生一些副作用,因为transform-styleperspectivetransform将影响position:absolute/fixed元素。相关链接:为什么在父元素上应用CSS过滤器会破坏子元素的定位?


3
很好,这个答案涵盖了所有文章中的不明显的细节以及我到目前为止读到的其他关于这个主题的答案。在此之前,我一直无法区分“堆栈上下文”、“定位元素”、“opacity形成堆叠上下文...(那么它是变成一个定位元素还是什么?)”以及z-index实际产生影响的情况等术语,等等。现在我知道有“堆叠水平”,“绘画顺序(比我想象中要复杂得多)”。阅读完这个答案后,我感觉自己现在掌握了关于堆叠上下文/绘画顺序/z-index的知识。 - Loi Nguyen Huynh
1
@aderchox HTML代码的书写顺序很重要。如果一个div在另一个div之后编写,那么它就会在树形结构中排在后面。如果一个div是另一个元素的子元素,那么父元素就是树形结构中的第一个元素,依此类推。 - Temani Afif
在第3步(绘画顺序)中,它说:“由具有负z-index(不包括0)的定位后代形成的堆叠上下文按z-index顺序(最负面的先)然后按树顺序排列。” 如果是这种情况,那么如果我们给body一个z-index为0,它应该仍然比子div(在我的问题中)晚绘制,子div是body的后代,因此仍然无法点击...我错在哪里?抱歉过多地评论,希望这有助于使其不那么模糊。此外,这是我删除的问题代码,以防需要:https://cdpn.io/e/oNXJvQg - aderchox
1
@aderchox 你不应该同时考虑绘画顺序的所有元素。如果你查看链接,你会读到以下内容:一个生成堆叠上下文(参见z-index属性)元素的后代的绘画顺序是...所以所有规则都适用于一个堆叠上下文内部。如果body没有应用z-index,它将不会创建一个堆叠上下文,并且将与其他元素在同一个堆叠上下文中,逻辑上那个(带有负z-index的元素)将先被绘制。 - Temani Afif
@aderchox 当body的z-index值不为auto时,它将创建一个堆叠上下文,这是完全不同的。首先绘制其中的内容(因此您的先前元素现在将属于其中),然后将body及其内部所有内容作为整体在body的堆叠上下文中绘制。如果您阅读我的答案,您还会发现:每个堆叠上下文都是自包含的:在元素内容堆叠之后,整个元素被认为处于父堆叠上下文的堆叠顺序中。 - Temani Afif
显示剩余4条评论

3

一个很好的思考方式是,每个父元素都包含自己的层叠上下文。兄弟元素共享一个父元素的层叠顺序,因此可能会互相重叠。

子元素总是根据其父元素获取一个层叠上下文。因此,需要使用负的z-index值将子元素“置于”其父元素(0)的层叠上下文之后。

唯一将一个元素从其父元素的上下文中移除的方法是使用position:fixed,因为这基本上迫使它使用窗口作为上下文。


我如何理解这样一个事实:如果父元素有自己的z-index,比如99,那么它永远无法覆盖其子元素?我编辑了问题标题以更明确地表达,但没有编辑问题内容。 - ZYinMD

2

这是一篇不错的文章。以下是相关部分:...都是DIV#3的子元素,所以这些元素的堆叠在DIV#3内完全解决。 - Bryce Howitson
2
谢谢您的回答,但它并没有回答问题的标题,很抱歉我稍微编辑了一下问题的标题,但没有编辑内容。 - ZYinMD

1

我如何通过逻辑理解这种行为?

对于我来说,很难通过逻辑理解你的问题。父元素包含其子元素。一个碗可以被另一个碗盖住。但是,除非你把汤倒出碗外,否则你不能用碗盖住汤。

z-Index设置了重叠元素的顺序。父元素无法覆盖其子元素。

在我看来,这完全是合乎逻辑的。


0

这个问题已经有了一个很好的答案,但我想再补充一点:如果你找到了这个问题,很可能你的问题应该通过overflow而不是z-index来解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接