鲜为人知的z-index

翻译 guokai 发表于 4 年前最后回复来自 qq2850071112 2 年前

关于z-index的问题在于很少有人真正理解它的原理。它并不复杂,但是如果没有花时间读过它的规范,毫无疑问有一些关键的方面你完全没有察觉到。

不相信我?好吧,尝试下解决下面这个问题:

问题

在下述HTML代码中有三个<div>元素,每个<div>元素包含一个<span>元素。每个<span>都各自有自己的背景色(分别是红色,绿色和蓝色)。每个<span>元素都绝对定位在接近文档左上角的地方,轻微的错开其它<span>元素就可以看到哪个在最顶端。第一个<span>元素的z-index值为1,另外两个没有设置z-index值。

下面是HTML和基本的CSS代码。同时也有一个含有完整CSS的demo(via Codepen)。

<div>
  <span class="red">Red</span>
</div>
<div>
  <span class="green">Green</span>
</div>
<div>
  <span class="blue">Blue</span>
</div>
.red, .green, .blue {
  position: absolute;
}
.red {
  background: red;
  z-index: 1;
}
.green {
  background: green;
}
.blue {
  background: blue;
}

给你的挑战是这样的:尝试不破坏下面的规则将红色的<span>元素置于蓝色和绿色<span>元素之下。

  • 不用任何形式改变HTML代码
  • 不在任何元素上添加/更改z-index属性
  • 不在任何元素上添加/更改position属性
  • 为了看到你是否能够解决,在Codepen上方点击编辑链接修改代码即可看到效果,如果你成功了,看起来应该是下面这个例子。

警告:不要点击下面例子中的CSS标签,否则会看到答案。

解决方案

解决方案就是给第一个<div>添加小于1的opacity值(红色<span>的父元素)。下面是添加在上边Codepen中的CSS代码:

div:first-child {
  opacity: .99;
}

如果你摸不着头脑,感到震惊且难以置信,为什么不透明度会对影响元素的层叠而显示在前面,欢迎来到俱乐部。当我第一次无意中发现这个问题后,我也同样感到震惊。

好在这篇文章的剩余部分能够让事情变得逐渐清晰起来。

层叠顺序

Z-index似乎如此简单:拥有较高z-index的元素层叠在较低z-index元素的前面,对吗?当然-不对。这只是z-index问题的一部分。它看起来如此简单,以至于众多开发者都没有花费时间去阅读相关的规则。

HTML文档中的每个元素都能在文档中其它元素的之前或者之后。这就是所谓的层叠顺序。

z-indexposition属性没有牵涉在一起的时候,规则就相当简单:基本上,层叠顺序同元素在HTML文档中的次序一致。(好吧,其实比这稍微复杂一点,但只要你不使用负边距重叠的内联元素,你可能不会遇到这样的极端情况。)

当你把position属性一起考虑进来,那么任何拥有定位的元素(及其子元素)都显示在任何非定位元素的前面。(一个“拥有定位”的元素指的是它有一个不为staticposition属性值,例如:relative, absolute等)

最后,一并考虑上z-index,事情就变得稍微有点诡异了。首先理所当然的认为较高z-index值的元素在较低z-index值元素之前,并且任何拥有z-index属性的元素在不具有z-index属性元素之前,但是并非这么简单。首先,z-index仅仅对于拥有定位的元素有效。如果你试图给一个没有指名position属性的元素设置z-index,将没有任何效果。其次,z-index值可以创建层叠上下文,现在看似简单的事情突然变得些许复杂了。

层叠上下文

一组拥有公共父级的元素在层叠顺序中一起上移或者下移被称为层叠上下文。透彻的理解层叠上下文是真正掌握z-index和层叠顺序如何工作的关键所在。

每个层叠上下文有一个单独的HTML元素作为它的根元素。当一个新的层叠上下文在一个元素上形成的时候,层叠上下文将其所有的子元素都限制在层叠顺序的一个特定位置。这意味着如果一个元素被层叠顺序底部的一个层叠上下文所包含,将没有办法将其置于另一个不同的具有更高层叠顺序的层叠上下文中包含的元素之前,即便这个元素有无限大的z-index值。

在一个元素上形成层叠上下文有如下三中方式:

  • 当一个元素是文档的根元素(<html>元素)
  • 当一个元素有除static之外的position属性值且z-index值不为auto
  • 当一个元素有小于1的opacity

前两种形成层叠上下文的方式很有意义,且一般Web开发者都能够理解(即使他们不知道它们的叫法)。

第三种方式(opacity)在W3C规范文档之外几乎从未被提及过。

在层叠顺序中确定元素的位置

其实确定页面上的所有元素的全局层叠顺序(包括边框,背景,文本节点等)极为复杂,远远超出了本文的范围(再次请您参考规范)。

但对于大多数的意图和目的,一个基本的对层叠顺序的了解还需要很长的路要走,有助于保持CSS开发的可预测。因此,让我们先来打破顺序分解成单独的层叠上下文。

具有相同层叠上下文的层叠顺序

下面是确定具有单一层叠上下文元素层叠顺序的基本规则(从后至前):

  • 层叠上下文的根元素
  • 拥有定位的元素(以及它们的子元素),z-index值为负(较高的值层叠在较低的值之前;相同值的元素根据HTML中的出现顺序层叠)
  • 非定位元素(根据HTML中的出现顺序层叠)
  • 拥有定位的元素(以及它们的子元素),z-index值为auto(根据HTML中出现顺序层叠)
  • 拥有定位的元素(以及它们的子元素),z-index值为正(较高的值层叠在较低的值之前;相同值的元素根据HTML中的出现顺序层叠)

注意:拥有定位且z-index值为负的元素在层叠上下文中被首先排序,这就意味着他们出现在其它所有元素之后。正因为如此,才使得一个元素在自身父元素之后成为可能,一般情况下这是不可能的。这仅仅当该元素的父元素不为根元素且同该元素处在同一层叠上下文中才有效。Nicolas Gallagher的CSS下拉阴影无图实现就是一个很好的例子。

全局层叠顺序

严格理解如何/何时形成新的层叠上下文,以及掌握层叠上下文的层叠顺序,找出一个特定元素在全局层叠顺序中的位置就并不那么糟糕了。

避免“被绊倒”的关键在于能够发现新的层叠上下文的形成。如果你给一个元素设置了10亿的z-index并且它并未在层叠顺序中向前移动,看下它的祖先元素树,如果它的任一父元素形成了层叠上下文,你的z-index设置了10亿也就没什么用了。

结束语

再回到原来的问题,我已经重新创建了HTML结构,在每一个标签旁边,都添加了注释,表明其在层叠顺序中的位置。这个顺序是假设使用原来CSS的前提下。

<div><!-- 1 -->
  <span class="red"><!-- 6 --></span>
</div>
<div><!-- 2 -->
  <span class="green"><!-- 4 --><span>
</div>
<div><!-- 3 -->
  <span class="blue"><!-- 5 --></span>
</div>

当我们给第一个<div>元素增加opacity规则时,层叠顺序就变成了这样:

<div><!-- 1 -->
  <span class="red"><!-- 1.1 --></span>
</div>
<div><!-- 2 -->
  <span class="green"><!-- 4 --><span>
</div>
<div><!-- 3 -->
  <span class="blue"><!-- 5 --></span>
</div>

span.red之前是6,但是现在变成了1.1。我用了.标记来表示一个新的层叠上下文的形成。且span.red现在是新的上下文中第一个元素。

希望红色盒子为什么落后于其它盒子的问题现在更加清楚一点了。原来的例子中只有两个层叠上下文,根和span.red元素上形成的。当我们给span.red的父元素添加opacity属性时,我们形成了第三个层叠上下文,因此,在span.red上的z-index值仅仅在新的上下文中生效了。因为第一个<div>(我们设置不透明度的元素)和其兄弟元素不具有position属性或设定的z-index值,其层叠顺序是由HTML中的源代码顺序决定的,这意味着第一个<div>,和其层叠上下文内包含的所有元素,渲染显示在第二个和第三个的<div>元素的后面。

共收到8条回复
chishang 4 年前 #1

opacity <1 也可以创建新的层叠上下文,是css3中新提出来的,所以知道的人不多:In future levels of CSS, other properties may introduce stacking contexts, for example 'opacity' [CSS3COLOR]

myljs 4 年前 #2

对,关于 opacity 的层叠问题,最近我也发现了,对于设置了 opacity < 1 的层,要比普通层更高,比设置了 position 及 z-index 的层要低一些。如果两个层都设置了小于1的 opacity 那么该咋地还是咋地。这是个 BUG?

PS:这文章字体太小了,特别是 《code》 标签里面的代码,更小,这么长的文章啊,看的眼花缭乱的。管理员大哥啊,能不能改大点啊

junji 4 年前 #3

learning

walleve 4 年前 #4

学习了半天..

ihuguowei 4 年前 #5

今天学写lightBox刚好遇到这个问题。

qq286735628 4 年前 #6

@myljs 如果有两个平级的opacity < 1 的层,则后面出现的挡住前面出现的。

你可以理解为,设置 opacity < 1时,该元素也被设置了z-index:auto或者z-index:0,然后你再进行比较就明白了

VeryCB 4 年前 #7

http://philipwalton.com/articles/what-no-one-told-you-about-z-index/

comalan 4 年前 #8

good~

登录后即可参与回复