CSS层叠上下文核心思想,一劳永逸搞定z-index

260 阅读4分钟

层叠上下文、z-index是面试中比较难的知识点,许多开发者都没有理解这些概念,导致项目中出现诡异的现象无法解决。

在翻阅了一些国人大佬的博客后,发现十分隐晦难懂,很啰嗦又抓不到重点。后翻阅老外博客、W3C标准,渐渐领悟了其中思想。本文不打算写成一个大而全的文章,无意取代其它资料,只是希望读者能理解最关键最核心的思想,之后再阅读其它资料便如鱼得水。

本文演示用的HTML

演示用的HTML如下:

<div class="a">
  我是A
  <div class="c">C是A的子元素</div>
  </div>
  <div class="b">
  我是B
</div>

.a {
  height: 150px;
  width: 150px;
}
.b {
  height: 80px;
  width: 200px;
}
.c {
  height: 100px;
  width: 100px;
}

两个大家已经掌握的“常识”

现在我们让它们层叠在一起。按照规则,后出现的元素会覆盖在先出现的元素之上。所以元素按照A、C、B的顺序进行层叠。最终B覆盖在A和C之上。

#demo2 .a {
  position: relative;
}
#demo2 .b {
  position: absolute;
  top: 60px;
}
#demo2 .c {
  position: absolute;
}

接下来我们通过z-index让C在B的前面,A保持不动,所以效果是“B插在了AC之间”。

#demo3 .a {
  position: relative;
}
#demo3 .b {
  position: absolute;
  top: 60px;
}
#demo3 .c {
  position: absolute;
  z-index: 1;
}

很多人以为z-index表示元素离屏幕的距离吧,以为z-index大的元素必定会覆盖小的元素吧?其实不是,这个误区是很多BUG的来源。

层叠上下文的原子性

现在,我们让A元素形成一个层叠上下文,方法有很多一般博客都有提到。这里我们就随便用个设置不透明度的方法:opacity: .9;。这时候z-index竟然就失效了,C跑到B的下面了,为何我们只是加了个透明度,布局就变了呢?

#demo4 .a {
  position: relative;
  opacity: .9;
}
#demo4 .b {
  position: absolute;
  top: 60px;
}
#demo4 .c {
  position: absolute;
  z-index: 1;
}

这是因为层叠上下文内具有原子性(atomic),其它元素无法“插入到AC之间”。 虽然C有z-index,但A和C是一个原子性整体,A在B下面,于是C也在B下面了(A是C的爸爸啊)。

「原子性」是很关键的概念,在W3C的原文是这样说的: A stacking context is atomic from the point of view of its parent stacking context; boxes in other stacking contexts may not come between any of its boxes.

「原子性」机制是为了简化浏览器实现,浏览器为不同层叠上下文分别渲染,最后把他们拼接起来。 拼接的时候浏览器只粗暴地考虑上下文之间整体叠放顺序,不再考虑不同上下文内部元素的顺序。 若没有「原子性」,浏览器将难以实现。

层叠顺序

在同一个层叠上下文中,子元素之间是按照什么顺序叠放的呢? 这点很多博客都总结了,我从这篇博客拷贝了下面这张图。类似的图还有很多,比如张鑫旭的博客也有一张差不多的图。

但是这些图总结的不全!请大家去翻W3C文档,下文我会再说到这个问题。

也就是说inline元素会显示在block元素的前面,我们把A改成inline,结果A压着B,效果和预期一致。

#demo5 .a {
  display: inline;
}
#demo5 .b {
  margin-top: -10px;
  margin-left: 10px;
}
#demo5 .c {
  position: absolute;
}

把A改回block,结果B压着A,效果和预期一致。

#demo6 .a {
  display: block;
  height: 25px;
}
#demo6 .b {
  margin-top: -10px;
  margin-left: 10px;
}
#demo6 .c {
  position: absolute;
}

但是凭什么C在B的上面呢?这是那张图没有提到的,因为那张图总结的不全,遗憾的是国内多数开发者没有追根溯源的精神,导致出了问题毫无头绪。W3C原文如下:

可以看到W3C说的更全面,元素是否定位也会影响层叠结果。下面我们把C的position注释掉,然后B就压着C了,和W3C描述的一致。

#demo7 .a {
  display: block;
  height: 25px;
}
#demo7 .b {
  margin-top: -10px;
  margin-left: 10px;
}
#demo7 .c {
  /* position: absolute; */
}

最后的思考练习

最后给大家的思考题,你知道为啥A、C都被B压着了,可是里面的文字还能显示出来吗?因为B是非定位元素,然而文字是inline,inline是在非定位元素前面的。

那么怎么让A、C的文字一起被B压着呢?方法有很多,大家思考,我这里随便给2个方法。

#demo8 .a {
  display: block;
  height: 25px;
}
#demo8 .b {
  margin-top: -10px;
  margin-left: 10px;
  /* 方法一 */ /* position: relative; */
  /* 方法二 */ transform: scale(1);
  /* 其它方法自己多思考 */
}
#demo8 .c {
}