• 作者:老汪软件技巧
  • 发表时间:2024-11-11 10:04
  • 浏览量:

image.png

默认情况下,网页内容是没有偏移角的垂直视觉呈现,当内容发生层叠的时候,一定会有一个前后的层叠顺序产生,而要理解网页中元素的顺序计算规则,就需要深入理解CSS中的层叠上下文和层叠顺序。

我们都熟悉CSS中的z-index属性,也经常使用该属性去设置元素的层级顺序,但z-index实际上只是层叠上下文相关的一个常用属性而已。

本文会以z-index属性开始,逐步理清层叠上下文。

1 一个z-index的例子1.1 介绍例子

z-index可以控制HTML元素的堆叠顺序,具有较高值的元素将显示在顶部:

image.png

代码详见这里

<div class="first box">元素1div>
<div class="second box">元素2div>
<style>
  /** 已剔除不相关CSS属性 */
  .box {
    position: relative;
  }
  .first.box {
    z-index: 2;
  }
  .second.box {
    z-index: 1;
  }
style>

因为 .first.box 的z-index比 .second.box 大,所以它堆叠在前面。如果我们删除.first.box 的z-index声明,则.first.box 会排在.second.box 后面。

然而,事情并不总是那么简单。有时,较大的z-index并不是显示在最前面。

image.png

代码详见这里

<div class="layer1">
  元素1
div>
<div class="layer2">
  元素2
  <div class="content">元素2的子元素div>
di>
<style>
/** 已剔除不相关CSS属性 */
.layer1 {
  position: relative;
  z-index: 2;
}
.layer2 {
  position: relative;
  z-index: 1;
}
.layer2 .content {
  position: absolute;   
  z-index: 999;
}
style>

.content 的 z-index 比 .layer1 大得多,但是.content却被.layer1遮挡。

为了解释这个现象,就需要先理解层叠上下文。

1.2 解释疑问

页面的图层就像Photoshop或Figma这样的图像编辑软件,如下面的页面就是一个个元素的图层组合而成,图层在Z轴上有前后顺序。

在这些程序中,我们还可以对元素进行分组(图中左侧层级的tree结构),每个元素都被包含在一个个组(图中左侧层级的div、section这些),针对于层叠顺序,不同组的元素之间无法直接比较,层叠顺序只能同组的元素间互相比较。

image.png

CSS层叠的工作原理也类似,元素都会被分组到层叠上下文中,当我们给一个元素设置z-index时,该值只与同一上下文中的其他元素进行比较。

默认情况下,一个HTML文档将包含一个涵盖所有节点的单一层叠上下文,但我们可以在里面继续创建其他的层叠上下文。

创建堆叠上下文有很多方法,但最常见的是:

/** `position`值为 `absolute`或 `relative`且`z-index`值不为 `auto` */
.some-element {
  position: relative;
  z-index: 1;
}

通过组合这两个声明,即可创建一个层叠上下文。

再回看前面的例子,代码映射的层叠上下文如下:

image.png

根据前面提到的“z-index值只与同一上下文中的其他元素进行比较”。.content元素的z-index为999,但该值仅与.layer2层叠上下文相关,它只能控制.content元素是显示在

标签的前面还是后面。

同时,在父级的层叠上下文中,.layer1和.layer2会被对比,由于.layer2的z-index较小,所以.layer2会被显示在.layer1的下面,而他们所有的子元素都会遵守这个规则。

1.3 修复问题

针对上面的例子,我们只需要取消.layer2的层叠上下文即可:

.layer2 {
  position: relative;
  /** 去除z-index,取消创建层叠上下文 */
  /* z-index: 1; */
}

如果没有z-index,.layer2 将不会创建层叠上下文。那层次结构会变成下面这样:

image.png

因为.layer1和.content处于同一个层叠上下文中,而.content的z-index大于.layer1,所以.content展示在前面。

image.png

需要注意的是,浏览器在层级比较规则上根本不关心父子层级,即便.content比.layer1嵌套的更深,浏览器只关心层叠上下文。

**Q:**在上面提及的 人为构造代码示例 中,因为.layer2元素中的z-index没有作用,所以我们可以移除.layer2元素的z-index来解决这个问题,但在实际场景中,可能会需要.layer2使用z-index创建层叠上下文,此时应该如何处理?**A:**根据CSS的规则,我们无法“摆脱”层叠上下文。一个层叠上下文内的元素永远无法与另一个层叠上下文内的元素进行比较。不过,我们仍然可以通过一些“非传统”的思维方式来实现期望的结果。我们可以通过将.content添加到标签来使其在.layer2之外展示,然后我们可以使用一些CSS来相应地定位它,使其看起来像是该元素的子元素。当然了,还有更好的做法,在本文的后面。

2 层叠上下文相关2.1 层叠上下文

在前面提到的Photoshop类比中,组和层之间有明显的区别,所有的视觉元素都是层,组可以被想象成包含它们的一个模块,它们是两个不同的概念。

在web开发中,当我们决定给一个元素设置z-index时,我们的目标通常是使该元素在父级层叠上下文中将该元素提升或降低到其他元素之上/之下,可因为层叠上下文的规则,我们在调整元素层级的时候,势必要考虑到在该元素上创建一个层叠上下文所带来的影响。

当创建一个堆叠上下文时,它会将其所有子元素“扁平化”。这些子元素仍然可以在内部重新排列,但我们基本上已经将这些后代锁定在内部了。就拿前面的例子来说:

<div class="layer1">
  元素1
div>
<div class="layer2">
  元素2
  <div class="content">元素2的子元素div>
di>

默认情况下,HTML元素将根据它们的DOM顺序堆叠。在没有任何CSS干扰的情况下, .layer2将呈现在.layer1之上。我们可以给.layer1设置一个z-index,把它提升到前面,但相应的,我们也把.layer1的子节点都提到了.layer2的前面。这就是层叠上下文机制中最需要注意的地方,也是这种机制导致了我们前面讨论的bug。

层叠上下文mdn_上下文是指_

我们不应该把z-index仅仅看作是改变元素顺序的一种方式。我们应该把它看作是改变元素及其子元素所形成的组的顺序的一种方式。

2.2 层叠水平

层叠水平是一个概念,有点像为了计算显示顺序而创造一个统一标准的计量单位,决定了同一个层叠上下文中元素在z轴上的显示顺序。

普通元素的层叠水平优先由层叠上下文决定,因此,层叠水平的比较只有在当前层叠上下文元素中才有意义。

需要注意的是,层叠水平和z-index属性不是等同的。z-index属性的对比仅仅是层叠水平的一部分,层叠水平作用范围是所有元素,而z-index的对比仅仅作用于那些z-index属性生效的元素(定位元素以及flex盒子的子元素)

2.3 层叠顺序

层叠顺序其实是一种规则,是元素发生层叠时Z轴显示顺序所依据的一种规则。

image.png

2.4 创建层叠上下文

页面根元素天生具有层叠上下文,称之为“根层叠上下文”。剩下的层叠上下文都是靠一些特定的CSS属性创建的。

在前面的例子中,我们是通过相对/绝对定位与z-index组合来创建堆叠上下文,但是这不是唯一的方法:

还有其他一些方法,你可以在MDN上找到。

3 实例说明3.1 改变层叠顺序也可以不使用z-index

前面提到创建层叠上下文的方法主要分为两类,一类是可以和z-index属性配合的,一类是不需要z-index属性就可以独立创建层叠上下文的。

所以,调整元素的层叠顺序也不只有通过z-index来实现。先来看一个例子:

代码详见这里

image.png

<div class="layer">
  <div class="content content1">元素1div>
  <div class="content content2">元素2p>
di>
<style>
/** 已剔除不相关CSS属性 */
.content2 {
  background: #bd334b;
  margin: -10px 0 0 -20px;
}
style>

默认情况下,HTML元素将根据它们的DOM顺序层叠,.content2的dom排在.content1的后面,所以展示的时候,.content2元素在上方,这是没有层叠上下文相关属性参与的默认情况。

此时,我们给.content1设置不大于1的opacity属性:

.content1 {
  opacity: 0.99;
}

如此一来,.content1元素就显示在.content2的上方,页面的层叠上下文关系也从左侧变化为右侧。

这是因为层叠上下文的层叠水平要比普通元素高,.content1元素因为设置了不大于1的opacity属性,变成了层叠上下文,而.content2仅仅是普通元素,所以.content1的层级水平更高,也就显示在前面。

image.png

3.2 z-index值不等于auto时的生效条件

继续上面的例子,给.content1、.content2的样式都设置opacity使其成为层叠上下文,设置z-index,让.content1的值大于.content2:

/** 已剔除不相关CSS属性 */
.content1 {
  opacity: 0.99;
  z-index: 2;  
}
.content2 {
  opacity: 0.99;
  z-index: 1;
}

结果如下所示,z-index并不是只要设置了就一定生效的,z-index值不等于auto时是有生效条件的(因为auto是默认的,所以一定生效)。

image.png

若z-index值不等于auto,仅在定位元素(position不等于static)以及flex盒子的子元素中才有效。

根据前面提到的创建层级上下文方法,给.content1和.content2父元素.layer设置display:flex,并给子元素设置z-index(值不是auto),即可使子元素成为层叠上下文,改造的代码如下:

代码详见这里

/** 已剔除不相关CSS属性 */
.layer {
  display: flex;
}
.content1 {
  opacity: 0.9; /** 添加opacity也不会造成影响 */
  z-index: 2;  
}
.content2 {
  opacity: 0.9; /** 添加opacity也不会造成影响 */
  z-index: 1;
}

.content1和.content2的层叠上下文被创建了,同时z-index也生效了,所以.content1在.content2展示了,实现了我们的预期效果。

image.png

3.3 没有直接设置z-index的层叠上下文如何计算层叠顺序

若普通元素通过前面提到的方法具有了层叠上下文,其层叠顺序就会变高,那它的层叠顺序究竟在哪个位置呢?

在回答这个问题之前要先说明创建层叠上下文一个特性,通过不依赖于z-index属性的创建层叠上下文的方法(例如小于1的opacity、will-change 设置了任一属性等),该层叠上下文的z-index=auto。

层叠顺序分两种情况讨论:

然后,就按照前面给出的层叠顺序示意图来对比,z-index值则对比大小。

4 结束语

在前端开发中,调整元素的层级顺序是常见且重要的任务。然而,很多人可能误以为修改z-index属性是唯一的解决之道,以及小看了调整层级所带来的影响。事实上,改变元素层级顺序的方法并不仅限于调整z-index值,深入理解和掌握层叠上下文的概念及其规则可以让我们更有把握的调整层级,了解其影响范围。

希望本文对你有帮助。

5 参考文章