我想更了解z-index

z-index是一个很常用的css属性,最初认为它的使用非常简单:positioned elements(除了static)设置z-index,z-index大的堆叠在z-index小的元素之上。在后面的学习与练习中便发现它并不是那么简单,于是去查阅了相关的资料,现在整理分享一波~

Questions

先看下面的代码:

1
2
3
4
5
6
7
8
9
<div>
<span class="first">我是萌萌A</span>
</div>
<div>
<span class="second">我是萌萌B</span>
</div>
<div>
<span class="third">我是萌萌C</span>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.first, .second, .third {
position: absolute;
height: 100px;
line-height: 100px;
width: 100px;
text-align: center;
}
.first {
top: 20px;
left: 20px;
background: #fdb;
z-index: 1;
}
.second {
top: 60px;
left: 60px;
background: #bda;
}
.third {
top: 100px;
left: 100px;
background: #9ac;
}

这个结果大家应该是可以想象出来的,很普通的一个东东,就是下面这样,我是demo

显然这不是我们的问题,真正的问题要登场了
在下列条件的约束下,你能让萌萌A到最后面去吗?也就是变成下图这样。

  • 不改变HTML结构
  • 不给任何元素添加或修改z-index属性,也就是保持萌萌A的z-index:1,其他任何元素的z-index为初始值auto
  • 不给任何元素添加或修改position属性

    想到方法了吗?如果你解决这个问题了,可以查看demo,看看是否与我给的出的方法一致。如果没有解决,那就继续往下看。

堆叠顺序Stacking Order

每一个在HTML文档中的元素既可以在其他元素的前面,也可以在其他元素的后面。这就是所谓的堆叠顺序。决定这个顺序的规则被十分清楚的定义在[CSS规范]中。
注意:此处的前面是指更接近用户因为用户面对屏幕

  • 当没有涉及z-index和position时,这个堆叠顺序的规则相当简单:基本上,堆叠顺序和元素在HTML中出现的顺序一样。(当你使用负margin时可能会出现不同于这个规则的情况)
  • 当有position属性介入时,那么所有定位了的元素(包括他们的子元素)会在没有被定位的元素前面。(此处一个元素定位了的意思指的是它有一个position属性,但是不是static,而是relative,absolute等)
  • 在position属性介入的基础上,再加上z-index属性的介入时,情况就变得有些怪异了。通常我们会认为z-index值越大的元素会越靠前,任何设置了z-index值的元素会在没有设置的元素的前面。但是事实上绝不是如此简单。
    • 首先,z-index属性只作用在被定位了的元素上。所以如果你在一个没被定位的元素上使用z-index的话,是不会有效果的。
    • 其次,z-index会创建一个堆叠上下文(Stacking Contexts),什么又是堆叠上下文呢?。

堆叠上下文Stacking Contexts

关于堆叠上下文的定义,由于本人翻译水平有限,感觉翻译过来之后总有些不对。所以贴上英语原文如下:

Groups of elements with a common parent that move forward or backward together in the stacking order make up what is known as a stacking context.

我的理解是:考虑最简单的情况,如下结构

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<div>
<p></p>
</div>
<ul>
<li></li>
</ul>
<p>
<span></span>
</p>
...
</body>

假设body的第一级子元素都创建了堆叠上下文(没有创建的时候就是一个普通的元素),当它们在堆叠顺序中向前或者向后移动时,会带着他们各自的子元素一起移动,我们可以想象将div(或ul或p)及其子元素p(或li或span)压缩成一个整体的层,
这个层就可以理解为堆叠上下文Stacking Contexts,堆叠上下文有堆叠顺序。
充分理解堆栈上下文是真正掌握z-index和堆叠顺序工作原理的关键。

现在我们来说说什么情况下会产生新的堆叠上下文:

  • 当一个元素位于HTML文档的最外层(元素)
  • 当一个元素被定位了并且拥有一个z-index值(不为auto)
  • 当一个元素被设置了opacity(值需要小于1),transforms, filters, css-regions, paged media等属性。堆叠顺序是z-index:auto可看成z:index:0级别
    通常来讲,如果一个CSS属性需要做一些特效的话,它都会创建一个新的堆叠上下文。

每一个堆叠上下文都有一个单独的HTML元素作为它的根元素。当一个新的堆叠上下文在一个元素E上形成时,那么它的所有子元素都会受到父元素(即元素E)的堆叠顺序影响。意味着如果一个元素位于一个离用户最远的堆叠上下文中,那你z-index设置得再大,哪怕是设为10亿,它也不会出现在其它堆叠上下文中的元素的前面。拿上面这张图解释就是,堆叠上下文C中的元素不可能出现在堆叠上下文A、B中的元素的前面。其实,说到这我深有体会,以前看一个元素就是不到别的元素前面来,我就加大z-index的值,可是毫无反应…..当时真是摸不着头脑。

用堆叠顺序决定一个元素的位置

影响堆叠顺序的因素有很多,想彻底了解它推荐你去看w3c规范,这里是基本了解这个顺序,它能够在很长一段时间内帮助我们提高CSS开发的可预测性。

在同一堆叠上下文中的堆叠顺序
下面是几条基本的规则,来决定在一个单独的堆叠上下文里的堆叠顺序(从远离用户到靠近用户)

  • 堆叠上下文的根元素(即创建堆叠上下文的元素)
  • 带着负z-index值的定位元素(和他们的子元素)(z-index高的值被堆叠在低值的前面;相同值的元素按照在HTML中出现的顺序堆叠)
  • 非定位元素(按照在HTML中出现的顺序排序)
  • 带着auto的z-index值的定位元素(和他们的子元素)(按照在HTML中出现的顺序排序)
  • 带着正z-index值的定位元素(和他们的子元素)(z-index高的值被堆叠在低值的前面;相同值的元素按照在HTML中出现的顺序堆叠)
    注解:带有负的z-index值的定位元素在一个堆栈上下文中先排序,这意味着他们出现在所有其他元素的后面。正因如此,它使一个元素出现在自己父元素之后成为可能,这以前通常是不可能的事。当然,这局限于它的父元素与它在同一个堆栈上下文,并且不是那个堆栈上下文的根元素。一个伟大的例子如Nicolas Gallagher CSS drop-shadows without images.

全局堆叠顺序
一个页面里可能会有很多个堆叠上下文,这些堆叠上下文也是有堆叠顺序的,堆叠上下文A在堆叠上下文B的前面,那么A中的元素就都会在B中的元素的前面,B中元素设置的z-index值再大也只会在B中有效,别的堆叠上下文根本不会知道你那么大。
理解了如何和什么时候会产生一个新的堆叠上下文,那么下次如果你遇到z-index值设了很大,但是不起作用的话就去看看它的祖先是否产生了一个新的堆叠上下文。

堆叠顺序的黄金准则

  • 谁大谁上:当具有明显的堆叠顺序标示的时候,如识别的z-indx值,在同一个堆叠上下文领域,堆叠水平值大的那一个在小的那一个前面。
  • 后来居上:当元素的堆叠顺序相同的时候,在DOM流中处于后面的元素会覆盖前面的元素。

我们回到最初的Question

还记得最开始的问题吗?忘记了的话点这里回去看一下,最开始问题的解答是给萌萌A的父元素加上一个’opacity: 0.99’,戳demo看一下
相信你已经知道为什么加上这一句萌萌A就害羞的躲到最后面去了吧。
一开始有两个堆叠上下文,一个由根节点产生,一个由设置了z-index:1并且position:absolute的萌萌哒A产生。
当我们设置了opacity时,产生了第三个堆叠上下文,并且第三个堆叠上下文把萌萌哒A产生的堆叠上下文包裹了,意味着萌萌哒A的z-index只在第三个堆叠上下文里面起作用。
而所有的<div>都没有定位或者z-index,所以他们的堆叠顺序按照HTML出现顺序排列,于是第三个堆叠上下文就去到下面。

更多

由于CSS3的出现,transforms, filters, css-regions, paged media等属性也可以创建堆叠上下文,情况变得有些许不一样。而我对CSS3并不是很熟悉,在此推荐张鑫旭前辈的博客,里面有一篇非常详细的,包含更多详情的相关文章,值得一看深入理解CSS中的层叠上下文和层叠顺序

参考资料

本文大部分为What No One Told You About Z-Index的译文,在该文章的基础上加入了一些自己的理解。