CSS z-index
提起,z-index大家脑海里可能会立刻浮现这样的知识点:“z-index 的值大小控制元素在 Z 轴上显示的层级,z-index 越大显示的层级越高(也就是在最上层,离观察者越近),没有指定的按照出现顺序堆叠,此外z-index不能跨父元素比较。
z-index 的使用似乎就是这么简单,对吧?
先看如下例 1:

  1. <div class="box box1">DIV#1,z-index为2</div>
  2. <div class="box box2">DIV#2,z-index为auto</div>

HTML 中有如下两个元素,DIV#1 的z-index为2,DIV#2 向右向上偏移。问:它们谁会显示在上面?
2021-05-21-22-15-52-396793.png
示例1 - 用法引导
如上所示,z-index为 2 的元素并没有显示在第二个元素上面。这似乎很奇怪,那到底是为什么呢?

一、没有使用z-index时,元素如何堆叠?

首先了解下默认情况下,元素的堆叠,即在没有使用z-index时,元素是如何堆叠的。
如果没有给任何元素指定 z-index,则元素按照如下顺序进行堆叠(由下到上,由远及近)。

  1. 根元素的背景和边框
  2. 非定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠
  3. 定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠

注:定位的元素即为 position 的值不是 static 的元素
2021-05-21-22-15-52-536419.png
示例2 - 无z-index时的默认堆叠
如上例 2 所示,定位的元素(DIV#1、DIV#2、DIV#3 与 DIV#4)按照出现的顺序堆叠。非定位的元素(DIV#5 与 DIV#6)虽然出现在后面,但是会被定位的元素遮盖,不过它们本身是按照出现顺序堆叠的。 :::tips 注意,当使用order属性改变flex元素子元素的出现顺序时,对于堆叠规则也有同样的影响。 ::: 如下例 3 所示,当将 DIV#2 的 order 改为-1 后,它出现的位置为第一个,其堆叠顺序也被 DIV#1 所遮盖。
2021-05-21-22-15-52-680035.png
示例3 - flex中order对出现顺序的影响

二、浮动块默认如何堆叠

如果存在浮动块,浮动块的堆叠顺序会介于无定位元素和定位元素之间。即:

  1. 根元素的背景和边框
  2. 非定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠
  3. 浮动块
  4. 定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠

稍微修改下示例 2 中的代码,将 DIV#1 和 DIV#3 改为浮动元素。可以看到如下例 4 所示,浮动元素的堆叠顺序高于非定位元素,低于定位元素。
2021-05-21-22-15-52-809687.png
示例4 - 浮动块的堆叠
此外,还有一点小改动,将非定位元素中的文本内容改为了左对齐,但其内容并没有被浮动元素覆盖。这其实是浮动元素的标准效果——环绕效果。这一行为也可以列为堆叠顺序之一。顺序如下:

  1. 根元素的背景和边框
  2. 非定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠
  3. 浮动块
  4. 非定位元素的后代行内元素
  5. 定位的后代块元素,按照在 HTML 中的出现顺序进行堆叠

为了让大家清晰的理解上面所说的非定位元素的后代行内元素。大家可以看下例 5。DIV#1 为浮动元素,所以其层级高于在其后出现的 DIV#2。此时 DIV#1 向右偏移,可以看见 DIV#2 中的行内文字元素(可以为纯文字节点)层级高于 DIV#1。
2021-05-21-22-15-53-017132.png
示例5 - 非定位元素的后代行内元素

三、使用z-index自定义堆叠顺序

以上是 CSS 中对于各类元素的默认排序,那能否自定义排序呢?答案显然是肯定的。使用z-index可以自定义堆叠顺序。
z-index的值可以为整数(正数、负数、0 均可)。使用方法很简单。
需要注意以下三点:

  1. 未指定z-index,默认为 auto
  2. 如果z-index相同,则按照默认规则比较
  3. z-index只能用于定位了的元素(暂时这么说,下文会追加解释)。这也解释了本文开头的例 1 为什么不生效了。因为z-index对普通元素没有效果。

如下例 6,修改了例 2 中元素的 z-index。
可以发现 DIV#5 和 DIV#6 并不受z-index的影响。主要体现在两个方面,首先 DIV#5 的z-index大于 DIV#6,但是显示却低于 DIV#6;其次是 DIV#5 的z-index小于 DIV#4,但是仍显示在其上面。
而对于定位的元素,z-index 对其有影响,堆叠顺序与数字大小符合。
2021-05-21-22-15-53-165736.png
示例6 - 使用z-index自定义堆叠顺序
相信通过上述内容,大家对于z-index应该有了一定的了解,但是以上仅仅是基本知识,关于堆叠远远没有这么简单。
想要彻底了解 z-index,还要了解一下 CSS 堆叠的一个重要概念————堆叠上下文。

四、堆叠上下文

堆叠上下文是 HTML 中的三维概念,它抽象出了一个 z 轴,z 轴垂直于显示器,指向用户(假设用户面朝显示区域)。
在前面的内容中,之所以有些元素的渲染顺序会受到z-index影响,是因为它们都因为某种原因产生了一个堆叠上下文,而不仅仅是上文提到的定位的元素。
那么到底什么情况下会产生堆叠上下文呢?其实堆叠上下文的生成主要受到元素的属性所影响。
如果任何一个元素满足一下条件之一,就会生成一个堆叠上下文。

  1. 文档的根元素(HTML)默认为一个堆叠上下文
  2. position值为”absolute“或”relative“,且z-index指定了除了 auto 以外值的元素
  3. position值为”fixed“或”sticky
  4. 弹性布局的子元素,且z-index指定了除了 auto 以外值的元素
  5. opacity的值小于1的元素
  6. mix-blend-mode的值不是normal的元素
  7. 以下属性值不为”none”的元素
    • transform
    • filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border
  8. isolation值为”isolate”的元素
  9. -webkit-overflow-scrolling值为”touch”的元素
  10. will-change指定了除初始值以外的任何属性的元素
  11. contain值为”layout”/“paint”及含义其中之一的组合值的元素

如上所述,有 11 种情况会生成堆叠上下文,对于堆叠上下文可以通过z-index指定其堆叠的顺序(注意这里不是上面说的只对定位元素生效了)。
对于堆叠上下文需要知道以下几点:

  1. 在每个堆叠上下文内部,子元素的堆叠规则遵循上面所讲的基本规则。
  2. 堆叠上下文可以包含在其他堆叠上下文内部,它们会创建一个堆叠上下文层级结构。
  3. 堆叠上下文的层级结构与 HTML 的元素不同,因为对于没有创建堆叠上下文的元素会被父元素同化。堆叠上下文的层级只包括创建了堆叠上下文的元素。
  4. 堆叠上下文独立于其兄弟元素,在处理自身内部堆叠时,只考虑其后代元素。内部堆叠完成后,将当前堆叠上下文当成一个整体,考虑在父堆叠上下文中的堆叠顺序。通俗的说,子堆叠上下文的z-index值只在父堆叠上下文中有意义。

注意,第四条和文章开头提到的“z-index 不能跨父元素比较”是不等价的,因为其限制了必须是堆叠上下文。
针对这几点,看一下例 7。
2021-05-21-22-15-53-365203.png
示例7 - 存在多级堆叠上下文时,元素的堆叠
示例 7 中,堆叠上下文的层级结构如下:

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

其中 DIV#4, DIV#5, DIV#6 是 DIV#2 的子元素,可见其堆叠顺序被限制在 DIV#2 中,z-index的值只在 DIV#2 内部有效,然后 DIV#2 又作为一个整体与 DIV#1,DIV#3 按照上述规则进行堆叠。
DIV#7 被根元素同化,DIV#8 与 DIV#1, DIV#2, DIV#3 按照上述规则进行堆叠。在其三之上。
其实有个小方法能够帮助大家更好地判断如何堆叠,那就是把堆叠上下文的层级结构类比为版本号。如下:

  • root
    • DIV#4 (V2.1)
    • DIV#5 (V2.3)
    • DIV#6 (V2.4)
    • DIV#1 (V3)
    • DIV#2 (V2)
    • DIV#3 (V1)
    • DIV#8 (V4)

如上,类比成版本号之后,就能很方便的判断出谁上谁下了。

五、注意事项

1. z-index: 0z-index: auto并不相同。

通常情况下,相邻的两个元素,如果其z-index值分别为0auto,看上去没什么区别的。如下例 8 所示。
DIV#1 的z-index值为 0,其堆叠顺序并没有高于 DIV#2,而是和出现顺序相同。
2021-05-21-22-15-53-491862.png
示例8 - zindex: 0 和 auto 的区别
但是实际上,这两种情况并不相同。上面提到,当元素”position值为”absolute“或”relative“,且z-index指定了除了 auto 以外值”时,元素会产生一个堆叠上下文,虽然元素本身堆叠顺序没有影响,但是其子元素的堆叠顺序会有影响。如下例 9 所示。
因为 DIV#1 的z-index值不为 auto,其产生了堆叠上下文,所以其子元素被限制在其内部,低于 DIV#2(如果z-index是 auto 的话,DIV#3 会高与 DIV#2)。
2021-05-21-22-15-53-609549.png
示例9 - zindex: 0 和 auto 的区别(2)

2. 不要滥用 z-index,将堆叠上下文的层级结构打平

之所以这样建议,是因为当堆叠上下文的层级结构比较复杂时,简单的修改某个元素的z-index或者其他属性,会导致一些无法预知的影响。
如下例时所示,DIV#2 是 DIV#1 的子元素,DIV#4 是 DIV#3 的子元素,DIV#1 和 DIV#3 不是堆叠上下文,则 DIV#2 与 DIV#4 的堆叠顺序与它们的z-index值对应。
2021-05-21-22-15-53-737208.png
示例10 - zindex造成的影响
但如果在某些时候需要调整 DIV#3 的 z-index,如将其调整成z-index: 4;,那么结果就完全不一样了。如下例 11 所示,DIV#4 高于 DIV#2 了。
2021-05-21-22-15-53-899771.png
示例11 - zindex造成的影响(2)
建议大家一定要慎用,基于对堆叠上下文的理解基础上,把握好页面中堆叠上下文的层级结构,尽量保持比较浅的层级结构,最好能与 HTML 层级结构一致,保证自己能够时刻知道如何进行修改与调整。

六、CSS元素遮挡问题与z-index

所谓元素遮挡问题,就是某些元素原本希望它出现在外层,因为定位的问题结果却被其他元素遮挡了。
怎么解决这个问题?
解决这个问题前,先要搞清楚浏览器渲染元素的规则

1、默认按元素出现先后顺序渲染

默认情况下,渲染元素时,浏览器按照元素的先后顺序进行渲染。

  1. <div class="box">
  2. <div class="d1">1</div>
  3. <div class="d2">2</div>
  4. <div class="d3">3</div>
  5. </div>

上面3个div按照先后顺序渲染,div1最先渲染,出现在最后面。
2021-05-21-22-36-41-454568.png

  1. body {
  2. background-color: #ccc;
  3. }
  4. .box {
  5. width: 300px;
  6. margin: 0 auto;
  7. display: flex;
  8. }
  9. .box div {
  10. height: 100px;
  11. width: 100px;
  12. background-color: #fff;
  13. border: 1px solid #000;
  14. }
  15. .d2 {
  16. margin-left: -20px;
  17. margin-top: 10px;
  18. }
  19. .d3 {
  20. margin-left: -20px;
  21. margin-top: 20px;
  22. }

来看一下样式代码,给每个div加上了边框,使用负外边距将div2和div3像左移动了20px。
注意,这里所有div元素都没有使用定位,3个元素按照先后顺序进行了渲染。div1 被 div2 遮住了,而div2被div3遮住了。这就是元素层叠问题。
如果希望div1显示在最前面,该怎么做呢?

2、先渲染非定位元素,再渲染定位元素

浏览器会先绘制所有非定位元素,然后绘制定位元素,默认情况下所有定位元素会出现在非定位元素前面。这里的定位元素是指只要position属性不是默认的static就表示设置了定位。
现在将div1使用relative定位,它就会在最后绘制,因此它将出现在最前面

  1. .d1{
  2. position: relative;
  3. }

2021-05-21-22-36-41-532358.png
绘制的顺序是2,3,1。因此,如果希望div1显示在最前面,只要给它一个relative的定位就能解决。

3、z-index 值越大,越后渲染

z-index 的值可以是任意整数,z-index值越大的元素,将显示在前面。

  1. .d2{
  2. z-index: 1;
  3. }

将div2的z-index值设置为1后,它就显示在最前面了。
2021-05-21-22-36-41-609152.png
如果是负整数,那么它将第一个被渲染,出现在最后面。

总结

元素会按照如下顺序从后往前叠放

  1. z-index 为负的元素
  2. 非定位元素
  3. 定位元素
  4. z-index 为正的元素

如果发现某个元素通过绝对定位放置在某个位置,但是屏幕上却没有显示出来甚至无法点击,有一种可能就是被其他元素遮住了。这时候可以按照上面的方法来将该元素调整显示顺序。