一篇文章彻底搞懂css中的层叠上下文、层叠次序,不要再被 z-index 搞的头疼,包含源代码,建议收藏

一、如何使用 z-index 

1.1 z-index 不生效的原因

在学习层叠上下文之前,我们应该都知道并且使用过 css 中 z-index 这个属性,每一个入门前端的小白肯定都知道使用 z-index 来调节一些 dom 元素的层级关系。

这里面说的层级关系就是我们视觉上看起来的 z 轴,靠近用户的叫做顶层/上层,远离用户的叫做底层/下层。

但是有的时候我们会发现给一个元素设置 z-index 并没有生效,比如说下面的这个 dom 结构:

	<div>
		<div>A</div>
		<div>B</div>
	</div>

这是一个简单的,没有任何 class 的dom 结构,我们直接给 A 元素设置 z-index: 1,发现在浏览器的开发者工具中这个属性为置灰的状态,也就是说并没有生效,鼠标 hover 提示如下:

翻译过来就是:给 position: static 属性的元素加上 z-index 并不会生效。

也就是说,并不是所有的元素加上 z-index 都会生效,z-index 的生效是有条件的,现在我们已经知道了第一个条件,那就是 position != static 。

我们也需要知道 static 是所有的 dom 的 position 属性的默认值,随便写一个 div ,它的 postion 默认就是 static, 此时直接给它增加 z-index 是无效的。

position 的默认值是 static

但是事情好像并没有那么简单,因为从我们的开发经验得知,大多数的时候我们根本不需要手动修改 dom 的 position 属性,可我们给一些 div 加上 z-index 它却切切实实的生效了,稀里糊涂的生效了,这怎么解释?

我们可以一一测试出所有使 z-index 生效的条件,我把这些条件分为两个模块,第一个模块是布局相关的属性,第二个是样式相关的属性。

先说结论,影响 z-index 生效的只有布局属性,下面我们一个一个看。

1.1.1 布局属性 vs 样式属性

虽然都是 css 的属性,其实都属于样式,但是我还是把这些属性分为了“布局属性”和“样式属性”

布局属性:影响布局的比如:

  1. width/height
  2. display
  3. postion
  4. margin/padding

样式属性:关系到页面好不好看的:

  1. color/background/broder
  2. opacity
  3. transform
  4. filter
  5. background-filter
  6. perspective

1.2 使 z-index 生效的属性

下面我们就来先总结一下能够使 z-index 生效的布局属性。

1.2.1 position != static

当元素 A 本身的 position 属性不是默认值值 static 时,我们就可以使用 z-index,还是刚才的例子,我们给 A 增加一个 position 属性,让他们的值分别为(1) relative、(2) absolute、(3) fixed、(4) sticky 发现 z-index 都生效了。

建议:所有的代码自己写一遍,自己在浏览器调试一下,印象更加深刻,不要只看我的截图!!

但是不要忘记了,如果我们设置的值是 static ,依旧是无法生效的。

我们得出来第一个结论,position != static 是一个使 z-index 生效的条件。

注意,到目前为止我们研究的还都只是怎么让 z-index 生效,还没有说到层叠上下文的任何东西。

1.2.1 flex 容器的直接子元素

使 z-index 生效的第二个条件是作为 flex 容器的直接子元素,注意是【直接子元素】,我们修改代码如下:

其实准确的说法是 flex 布局的项目,flex 布局中,display: flex 的元素的【直接子元素】就称之为 flex 布局的项目。

<div style="display: flex">
	<div style="z-index: 1">A</div>
	<div>B</div>
</div>

我们给 A 元素的父元素增加属性 display: flex,使 A 元素成为了 flex 容器的子元素

注意如果父元素的 display: inline-flex 也可以。

然后我们再给 A 元素增加 z-index: 1,可以发现它生效了。

现在我们有第二个结论,flex 容器的子元素是 z-index 生效的条件。

哎?那我就有问题了,如果给 A 元素的父元素的父元素增加 display: flex,而 A 的直接父元素不设置 display: flex,那么 A 元素的 z-index 会生效吗?

<div style="display: flex">
	<div>
		<div style="z-index: 1">A</div>
		<div>B</div>
	</div>
</div>

看起来并没有生效,看来有这个疑惑的本质是对“子元素”这个词的理解,没有那么透彻,记住,只能是直接子元素

1.2.3 gird 容器的直接子元素

这一点和 flex 布局类似,当且仅当元素 A 是 grid 容器的的【直接子元素】时 A 元素的 z-index 会生效。

<div>
	<div style="display: grid">
		<div style="z-index: 1">A</div>
		<div>B</div>
	</div>
</div>

下面这样的代码,也不会生效,这一点和 flex 布局是一样的。 

<div style="display: grid">
	<div>
		<div style="z-index: 1">A</div>
		<div>B</div>
	</div>
</div>

1.3 z-index 的概念

其实我们应该在最开始的时候就看一下 z-index 在官方文档中的概念,

css z-index 属性设置【定位元素及其后代元素】或【flex项目/grid项目】的 z 轴顺序。z-index 较大的重叠元素会覆盖较小的元素。

The z-index CSS property sets the z-order of a positioned element and its descendants or flex and grid items. Overlapping elements with a larger z-index cover those with a smaller one.

注意:这里说的 flex 项目就是我们前面说的 flex 的直接子元素。然后我发现在 mdn 的中文版本中(截止到2025年3月31日)没有提到 grid 布局,所以还是要看英文文档的。

z-index - CSS:层叠样式表 | MDNCSS z-index 属性设置定位元素及其后代元素或 flex 项目的 Z 轴顺序。z-index 较大的重叠元素会覆盖较小的元素。https://developer.mozilla.org/zh-CN/docs/Web/CSS/z-index

1.3.1 z-index 的默认值 auto

我们要知道 z-index 的默认值是 auto。

那么我还有一个问题,就是如何知道一个css 属性的默认值呢?

除了查询官方文档,我们直接通过开发者工具其实也不容易知道一个元素的默认值,假设我们有如下的简单的代码:

<div>
	<div>A</div>
	<div>B</div>
</div>

现在我想知道 A 元素的 z-index 的默认值,打开开发者工具,选中元素 A, 无论是在 elements -> styles 

还是在 elements -> computed

或者是 elements -> properties 

都没有默认值,所以我们只能通过 js 层面来查询,可以使用 window.getComputedStyle 这个方法获取某个属性的默认值。任何 css 属性的默认值都可以用这个方法获取。

// 这里面 $0是指的在开发者工具中选中的元素
window.getComputedStyle($0)['zIndex']

需要重温一下浏览器开发者工具中$0 - $4 的用处。

1.3.2 z-index 的作用

在 mdn 的官方中写到,对于定位元素(即 position!=static), z-index 属性会:

  1. 指定盒子在【当前层叠上下文】中的层叠等级
  2. 指定盒子是否会创建局部【层叠上下文】

1.3.3 z-index 的取值

z-index 属性可以被设置为关键字 auto 或者数字,其中数字可能是负数、0、正数。

当然也不能无限大无限小,z-index 的取值是有范围的,在实际应用中很少用负数和0,正整数不超过4位数,即 9999

W3C标准

  • 类型:<integer>(整型)
  • 有效范围:理论上是-21474836482147483647(32位有符号整数)
  • 最大值2147483647(10位数字)

还需要注意 z-index: auto 和 z-index: 0 是有区别的。

当 z-index 为<integer>数字的时候,盒子在当前层叠上下文的层叠等级就是<integer>的值,盒子还会创建一个局部层叠上下文,者意味着该元素的后代不会和该元素的外部元素比较 z-index.

我们终于提到层叠上下文这个概念了,但是目前我们可能还不知道他是个啥东西,只知道 z-index 这个属性会影响它。没关系我们再从层叠上下文这个概念入手。

二、层叠上下文的创建

我们可以先不管 z-index 对层叠上下文的影响,先了解一下什么是层叠上下文。根据 mdn 官方文档的概念,我们总结一下。

层叠上下文是 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想,众 HTML 元素基于其元素属性按照优先级顺序排列。

我们开发者需要知道是如何让这些 html 元素按照我们想要的顺序在 z 轴上排列,我们来决定谁在上面,谁在下面。

层叠上下文 - CSS:层叠样式表 | MDN我们假定用户正面向(浏览器)视窗或网页,而 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想。众 HTML 元素基于其元素属性按照优先级顺序占据这个空间。https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_positioned_layout/Stacking_context

2.1 根层叠上下文 html 标签

首先我们应该记住一个根层叠上下文,就是 html 标签构成的一个默认的层叠上下文,任何一个 html 文档,我们不设置任何样式,都会有一个根层叠上下文。

我们有如下简单的代码,我们可以说:

  1. 元素 A 在根层叠上下文中
  2. 元素 B 在根层叠上下文中
  3. A 和 B 在同一个层叠上下文中

2.2  定位元素创建的层叠上下文

定位元素构成的层叠上下文又分为两种,一种是需要结合 z-index 才能生效,另一种仅定位元素自身就可以生效。

2.2.1 绝对定位/相对定位 + z-index !=auto

position 为绝对定位 absolute 或者相对定位 relative  且 z-index 的不为 auto 的元素构成一个局部的层叠上下文,这里有几个需要注意的点:

  1. z-index != auto ,即该元素的 z-index 不是默认值
  2. position=absolute/relative 必须给元素自己设置

在很长一段时间,我一直以为 position 的值设置为 relative 的时候不会对布局有任何影响,现在才发现其实不然,如果真的不影响布局,那 position 的默认值应该变成 relative 才对。

(1) 普通文档流的层叠顺序

假设我们有如下的代码:

<template>
	<div>
		<div class="A">A</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
	position: absolute;
	left: 0;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
}
</style>

我们没有给 A 和 B 元素设置 z-index ,而仅仅设置了 postion: absolute 绝对定位方便我们看视觉效果,所以现在 A 和 B 并没有创建新的层叠上下文,他们都属于根层叠上下文。页面布局如下所示, B 完全覆盖了A,因为根据正常文档流的排列规则,后面的元素 B 会覆盖前面的 A 元素。

(2)给 A 增加 z-index: 1

现在给 A 增加 css 属性 z-index: 1

<template>
	<div>
		<div class="A" style="z-index: 1">A</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
	position: absolute;
	left: 0;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
}
</style>

根据上面的理论知识,我们知道绝对定位 + z-index=1 会创建新的层叠上下文,那么现在页面上会怎么展示?

答案是 A 会在 B 的上面

此时的页面中的层叠上下文布局如下:

  1. 元素 A 在根层叠上下文中
  2. 元素 B 在根层叠上下文中
  3. A 和 B 在同一个层叠上下文中
  4. 由于A 的 z-index=1  大于 B 的 z-index: auto(默认值),所以 A 在 B 的上面

哎?不对吧,不是说绝对定位 + z-index!=auto 创建了新的层叠上下文了吗,到底怎么用呢?

没错,A 元素确实在 position: absolute 和 z-index:1 的作用下创建了新的局部的层叠上下文,但是注意!!!A 创建的层叠上下文影响的是 A 的子元素。而不影响 A 自身!!。

我们来看下面的代码,先说结论,根据 z 轴距离用户的远近:

A-1 > A > B  

黄色 > 红色 > 蓝色

<template>
	<div>
		<div class="A" style="z-index: 1">
			A
			<div class="A-1" style="z-index: 99">A-1</div>
		</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
	position: absolute;
	left: 0;
}
.A-1 {
	width: 300px;
	height: 80px;
	background: yellow;
	position: absolute;
	left: 0;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0; 
}
</style>

让我们来解释一下

<template>
	<!-- 下面的 div 位于 html 根层叠上下文 -->
	<div>
		<!-- A 位于根层叠上下文,但是 A 创建了新的层叠上下文 #A -->
		<div class="A" style="z-index: 1">
			A
			<!-- A-1 位于 A 创建的新层叠上下文 #A 中 , A-1 同时创建了新的层叠上下文 #A-1 -->
			<div class="A-1" style="z-index: 99">
				A-1
				<!-- 如果 A-1 中也有子元素,那么它处于 A-1 创建的新的层叠上下文 #A-1中 -->
			</div>
		</div>
		<!-- B 位于根层叠上下文-->
		<div class="B">B</div>
	</div>
</template>

此时 dom 中的层叠上下文:

  1. 元素 A 在根层叠上下文中
    1. 元素 A -1 在 #A-1 层叠上下文中
  2. 元素 B 在根层叠上下文中
  3. A 和 B 在同一个层叠上下文中
  4. 由于 A 的 z-index=1  大于 B 的 z-index: auto(默认值),所以 A 在 B 的上面

那么 A-1 和 A 怎么层叠就要引出关于层叠上下文第一个规则了。

子元素的层叠上下文受限于父元素的层叠上下文,我们到后面再说。我们先把能够构成层叠上下文的规则都例举完。

2.2.2 固定定位 fixed / 粘滞定位 sticky

position: fixed 或 position:sticky 的元素,只需要这一个 position 属性就可以创建新的层叠上下文,而不需要管 z-index 的值。这相对于 2.2.1 中的两种定位更加高端了。

请看下面的代码,我们只把 A 的定位改成了 fixed,然后去掉了 A 的 z-index :

<template>
	<!-- 下面的 div 位于 html 根层叠上下文 -->
	<div>
		<!-- A是 fixed 定位:A 位于根层叠上下文,但是 A 创建了新的层叠上下文 #A -->
		<div class="A">
			A
			<!--A-1还是 absolute 定位: A-1 位于 A创建的新层叠上下文文 #A 中 , A-1 同时创建了新的层叠上下文 #A-1 -->
			<div class="A-1" style="z-index: 99">
				A-1
				<!-- 如果 A-1 中也有子元素,那么它处于 A-1 创建的新的层叠上下文 #A-1中 -->
			</div>
		</div>
		<!-- B 位于根层叠上下文-->
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
	position: fixed; // 这里只把 A 改成固定定位
	left: 0;
}
.A-1 {
	width: 300px;
	height: 80px;
	background: yellow;
	position: absolute;
	left: 0;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
}
</style>

先说结论,根据 z 轴距离用户的远近:

B  > A > A-1

蓝色 >黄色 > 红色

 此时 dom 中的层叠上下文的:

  1. 元素 A 在根层叠上下文中
    1. 元素 A -1 在 #A-1 层叠上下文中
  2. 元素 B 在根层叠上下文中
  3. A 和 B 在同一个层叠上下文中
  4. 由于 A 和 B 都没有设置 z-index 属性,都是默认为 auto,所以渲染顺序,按照普通文档流的规则,后面的元素在上面,所以 B 在 A 的上面。
  5. 又因为 A-1 在 #A-1 的层叠上下文中,受限于父元素(A)的层叠上下文,所以 A-1永远在 A的里面,即便是 A-1 的 z-index: 99, A-1 也不会盖在 B 上面。

此时,如果我们给 A 设置 z-index 是数字,且大于 0,就可以实现 A 和 A-1 都在 B 上面,修改的代码如下:

强调设置的 A 的 z-index 的数字大于 0,是因为设置 z-index: 0 或者为负值(如 z-index: -1) 或者 z-index: auto。都是没办法实现 A 覆盖 B 的。

因为 z-index 的默认值是 auto ,z-index: 0 和 z-index: auto 在这个场景看起来相同,但是本质是有不同的。

  1. 单纯考虑层叠顺序时, z-index:auto 和 z-index:0 可以被视作同一个层级。
  2. 但是 z-index: auto 不创建层叠上下文
  3. z-index: 0 在某些情况下(比如某元素设置了定位属性)会创建新的层叠上下文

不要慌,关于这点,我们后面还得细说的。

效果就变成这样了:

2.3 布局属性创建的层叠上下文

我们在第一章知道了两个布局属性 display: flex/inline-flex 和 display: grid,这两个布局属性的直接子元素,如果设置了 z-index = 数字,那么这个子元素的 z-index 属性就会生效。

同样的,flex 布局和 grid 布局的直接子元素如果设置了 z-index= 数字,那么就创建了一个新的层叠上下文。 

<template>
	<!-- 下面的 div 位于 html 根层叠上下文 -->
	<div>
		<!-- A 位于 html 根层叠上下文 -->
		<div class="A" style="display: flex">
			A
			<!-- A-1 位于根层叠上下文, A-1 同时创建了新的层叠上下文 #A-1 -->
			<div class="A-1" style="z-index: 1">
				A-1
				<!--  A-1-1 处于 A-1 创建的新的层叠上下文 #A-1 中 -->
				<div class="A-1-1" style="z-index: 99">A-1-1</div>
			</div>
		</div>
		<!-- B 位于根层叠上下文-->
		<div class="B" style="z-index: 2">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
}
.A-1 {
	width: 300px;
	height: 80px;
	background: yellow;
}
.A-1-1 {
	width: 500px;
	height: 200px;
	background: pink;
	position: absolute;
	left: 0;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
}
</style>

先说结论,根据 z 轴距离用户的远近:

B  > A-1-1 > A-1 > A

蓝色 > 粉色 > 黄色 > 红色

 此时 dom 中的层叠上下文:

  1. 元素 A 在根层叠上下文中
    1. 元素 A -1 位于根层叠上下文中
      1. A-1-1 位于 A-1 创建的层叠上下文 #A-1中
  2. 元素 B 在根层叠上下文中
  3. A 和 A-1 和 B 在同一个层叠上下文中(即根层叠上下文)
    1. 由于 B 的 z-index: 2,A 的 z-index:auto,  A-1 的z-index: 1
    2. B 的 z-index 最大,所以在根层叠上下文中 B 在最上面
  4. 又因为 A-1-1 在 #A-1 的层叠上下文中,受限于父元素(A-1)的层叠上下文,所以 A-1-1 永远在 A-1 里面,即便是 A-1-1 的 z-index: 99, A-1-1 也不会盖在 B 上面。

这个例子中,我们给 A 元素设置了 display:flex,同样的给 A 设置 display:inline-flex 或者display:grid。 都能达到效果,所以就不再举详细的例子了。

2.4 样式属性创建的层叠上下文

我们从官方文档中可知,能够创建层叠上下文的属性还有

  1. opacity 属性小于1
  2. mix-blend-mode 值不为 normal
  3. 以下属性值不为 none 的元素
    1. transform
    2. filter
    3. backdrop-filter
    4. perspective
    5. clip-path
    6. mask/mask-image/mask-border
  4. isolation 属性值为 isolate 的元素
  5. 设置了 will-change
  6. contain 属性为 laytou、paint 或包含它们其中之一的合成值(比如 contain: strictcontain: content)的元素。

我们从其中选取两个常用的属性来看一下怎么回事。

2.3.1 opacity 属性小于1

请看下面的代码,只需要给 A 元素设置一个属性 opacity: 0.8 ,不透明度小于 1 ,就可以创建新的层叠上下文。

<template>
	<div>
		<div class="A" style="opacity: 0.8">
			A
			<div class="A-1" style="z-index: 99">A-1</div>
		</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
}
.A-1 {
	width: 300px;
	height: 80px;
	background: yellow;
	position: absolute;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
	top: 30px;
}
</style>

先说结论,根据 z 轴距离用户的远近:

B  > A-1 > A

蓝色 > 黄色 > 红色'

此时 dom 中的层叠上下文:

  1. 元素 A 在根层叠上下文中
    1. 元素 A-1 位于 A 创建的层叠上下文 #A 中
  2. 元素 B 在根层叠上下文中
  3. A  和 B 在同一个层叠上下文中(即根层叠上下文)
    1. 由于 A 和 B 都没有设置 z-index 默认为 auto
    2. 按照文档流的层叠顺序后面的 B 覆盖了 A
  4. 由于 A 创建了新的层叠上下文
    1. A-1 层级受限于 #A 层叠上下文
    2. 所以即便设置了 z-index:99 ,A-1 也无法覆盖 B

我们在开发者工具手动去掉 A 的 opacity 属性,效果如下:A-1 > B > A

此时,A 和 A-1 和 B 都在根层叠上下文中,z-index 最大的元素 A-1 自然在最上面,然后是按照文档流的层叠顺序,B 覆盖了 A。

如果我们再把 A 的 opactiy 属性设置为1,发现现象和去掉一样,所以我们这里要强调,只有 opactiy 的值小于 1 的时候才能创建层叠上下文。

2.3.2 transform 属性不等于 none

同样 tranform 属性值不等于 none 也可以构成新的层叠上下文,他的逻辑和上面2.3.1 中的 不透明度是一样的,这里就不再重复说明。

<template>
	<div>
		<!-- A 位于根层叠上下文,并且创建了层叠上下文 #A -->
		<div class="A" style="transform: translateX(10px)">
			A
			<!-- A-1 位于 #A 层叠上下文呢 -->
			<div class="A-1" style="z-index: 99">A-1</div>
		</div>
		<!-- B 位于 根层叠上下文 -->
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
}
.A-1 {
	width: 300px;
	height: 80px;
	background: yellow;
	position: absolute;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
	top: 30px;
}
</style>

B  > A-1 > A

蓝色 > 黄色 > 红色'

2.5 不同层叠上下文的对比

至此,我们总结了层叠上下文的创建条件,同时提到了一个知识点“子元素的层级”受限于父级层叠上下文。这样对于不同的层叠上下文之间,我们基本知道了怎么对比不同元素的层级。

我们可以用几个简单的例子来检验一下我们的学习成果。

2.5.1 嵌套很深的 dom 对比

只说结论:

不管 dom 层级嵌套的多深,我们只看是否在同一个层叠上下文,下面的的代码中

B > A-1-1-1-1 > A

蓝 > 黄 > 红

<template>
	<div>
		<div class="A">
			A
			<div>
				A-1
				<div>
					A-1-1
					<div>
						A-1-1-1
						<div style="opacity: 0.5">
							A-1-1-1-1
							<div class="inner" style="z-index: 99">A-1-1-1-1</div>
						</div>
					</div>
				</div>
			</div>
		</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
}
.inner {
	width: 300px;
	height: 80px;
	background: yellow;
	position: absolute;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 0;
	top: 30px;
}
</style>

2.5.2 让文档流中前面的元素覆盖后面的元素

下面的代码中 A 在文档流中属于 “前面的元素”,B 属于“后面的元素”

<template>
	<div>
		<div class="A">
			A
			<div>
				A-1
				<div>
					A-1-1
					<div>
						A-1-1-1
						<div style="transform: translate(10px)">
							A-1-1-1-1
							<div class="inner" style="z-index: 99">A-1-1-1-1</div>
						</div>
					</div>
				</div>
			</div>
		</div>
		<div class="B">B</div>
	</div>
</template>
<style lang="scss" scoped>
.A {
	width: 100px;
	height: 100px;
	background: red;
}
.inner {
	width: 300px;
	height: 80px;
	top: -20px;
	background: yellow;
	position: fixed;
}
.B {
	width: 200px;
	height: 200px;
	background: blue;
	position: absolute;
	left: 20px;
	top: 30px;
}
</style>

现在页面显示如下图

我现在想让 A 覆盖 B,即红色覆盖蓝色的块,最少的改动是什么?

答:由于 A 和 B 同属于根层叠上下文,所以只需要给 A 增加 z-index 为正整数即可,但是要注意直接给 A  增加 z-index 属性并不会生效,所以必须增加两个属性 position 和 z-index,如下图:

三、相同层叠上下文中的层叠顺序

上面一小节中我们知道了不同层叠上下文之间元素的层叠关系,总结下来就一句话:

当父级创建层叠上下文后,子元素的层叠关系受限于父级

那么我们就开始研究相同层叠上下文中元素的层叠顺序。

3.1 文档流后面的元素在上面

在我们完全不设置任何样式的情况下,在 dom 树中后写的元素一定是覆盖在前面元素的上面的,如下面的代码,B 覆盖 A,

如果前面的元素 A 有子元素,那么子元素肯定也会被后面的元素 B 覆盖:

3.2 z-index 的比较

3.2.1 z-index 大的在上面

这是一条总所周知的规则,z-index 大的元素会覆盖在 z-index 值为默认 auto 或者小的元素上面。

注意这里比较了 z-index ,那么就意味着设置的 z-index 一定是生效的,请再次回忆一下 z-index 的生效条件。

而且在同一个层叠上下文中比较 z-index ,是跨越 dom 树的。比如下面的代码,即便 A-1是 A 的子元素,然后 A  和 B 是兄弟节点。 A -1 和 B 也可以做 z-index 的比较。

A-1 的 z-index 我们设置了 1, 大于 B 的默认值 auto,所以结果就是

A -1 > B > A

黄 > 蓝 > 红

问题:能不能在上面的代码基础上,使层叠效果是 

A > B > A-1

红 > 蓝 > 黄

答案是不能。

因为,我们想让红A > 蓝B,能想到的就是调整 红A 的 z-index :1。但是这样的话 A 就创建了一个新的层叠上下文,他的子元素 A-1 就会永远被 A 控制住,永远在 B 的上面。

3.2.2 z-index 为 0

我们有如下的代码,没有设置任何 z-index,两个元素都在根层叠上下文中,且 z-index 都是默认值。

我们分别改变 B 和 A 的 z-index 改成 0

发现布局并没有任何变化,这说明 z-index: 0 和 z-index: auto(默认)的层叠规则是相同的,也就是优先级是一样的。

但是我们又需要区分二者,是因为 z-index:0 会创建新的层叠上下文。

3.3.3 z-index 为负数

z-index 为负数和为 0 或者是其他正数是一样的,直接比较谁大谁就在上面。

3.3 层叠顺序

我上面的例子中为了方便看都是使用背景色来展示层叠关系的,但是其实背景色和其他样式、属性之间的层叠关系还是有学问的。

深入理解CSS中的层叠上下文和层叠顺序 « 张鑫旭-鑫空间-鑫生活 本文是最近1-2个月最用心的文章,理解本文内容,就会明白网页中元素层叠的时候为什么会这样表现了,相信本文的内容一定会对您的学习有所帮助的。 https://www.zhangxinxu.com/wordpress/2016/01/understand-css-stacking-context-order-z-index/comment-page-1/可以先看一下这篇文章中的图片展示了在一个层叠上下文中一个完整的层叠顺序表,下面是由底层到顶层的层叠顺序:

  1. background/border
  2. 负 z-index
  3. block 块状水平盒子
  4. float 浮动盒子
  5. inline / inline-block 水平盒子
  6. z-index: auto 或 z-index:0 ,或者不依赖 z-index 的层叠上下文
    1. 不依赖 z-index 的层叠上下文,包括我们 2.2、2.3、2.4 中说的
  7. 正 z-index 

大佬的文章很详细,我暂时没有更好的例子,等有了再补充。

四、项目中的应用

说了很多理论知识,除了可以帮我们排查问题之外,我们在开发一个项目的时候,有些可以在开发的时候就注意的点。

4.1 主要布局中的元素都设置 z-index

假设我们有一个这样布局的网站,已知 left 模块 和 right-header 模块都有一个 box-shadow ,要求 left 在最顶层,然后是 right-header ,right-bottom 在最底层。

布局的代码如下:

<template>
	<div class="wrapper">
		<div class="left">left</div>
		<div class="right">
			<div class="right-header">right header</div>
			<div class="right-bottom">right-bottom</div>
		</div>
	</div>
</template>
<style lang="scss" scoped>
.wrapper {
	width: 100vw;
	height: 100vh;
	border: 1px solid;
	display: flex;
	align-items: stretch;
	justify-content: stretch;
	color: #fff;
	.left {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 200px;
		background-color: red;
	}
	.right {
		display: flex;
		align-items: center;
		flex-direction: column;
		justify-content: stretch;
		flex-grow: 1;
		.right-header {
			width: 100%;
			height: 64px;
			background: blue;
			display: flex;
			align-items: center;
			justify-content: center;
		}
		.right-bottom {
			flex-grow: 1;
			width: 100%;
			background: green;
			display: flex;
			align-items: center;
			justify-content: center;
		}
	}
}
</style>

要实现我们 left > right-header  > right-bottom 这种层叠关系,一般我们都能想到的改动是:

  1. 给 left 增加 z-index: 2
  2. 给 right-header 增加 z-index: 1

然后就完事了,没错这样会实现我们的需求,但是会有潜在问题。

因为我们没有给 right-bottom 设置 z-index ,这导致 left 、right、right header、right bottom 都处于一个层叠上下文,即根层叠上下文中。

如果 right bottom 新增一个元素 right-bottom-inner 设置了 z-index: 3,那么新增加的这个元素也属于根层叠上下文,又因为它的 z-index=3 > left =2,所以 right-bottom-inner 会覆盖 left ,但这很可能不是我们想要的,我们希望 left 始终在最上面

代码如下:

<template>
	<div class="wrapper">
		<div class="left">left</div>
		<div class="right">
			<div class="right-header">right header</div>
			<div class="right-bottom">
				right-bottom
				<div class="right-bottom-inner">right-bottom-inner</div>
			</div>
		</div>
	</div>
</template>
<style lang="scss" scoped>
.wrapper {
	width: 100vw;
	height: 100vh;
	border: 1px solid;
	display: flex;
	align-items: stretch;
	justify-content: stretch;
	color: #fff;
	.left {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 200px;
		background-color: red;
		z-index: 2;
	}
	.right {
		display: flex;
		align-items: center;
		flex-direction: column;
		justify-content: stretch;
		flex-grow: 1;
		.right-header {
			width: 100%;
			height: 64px;
			background: blue;
			display: flex;
			align-items: center;
			justify-content: center;
			z-index: 1;
		}
		.right-bottom {
			flex-grow: 1;
			width: 100%;
			background: green;
			display: flex;
			align-items: center;
			justify-content: center;
			flex-direction: column;
			.right-bottom-inner {
				display: flex;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: 500px;
				background: pink;
				z-index: 3;
				position: relative;
				left: -30px;
			}
		}
	}
}
</style>

该怎么改呢?有的人可能给 right-bottom 增加一个 overflow:hidden。但是这会改变它原来的布局,万一人家要滚动呢。

所以最简单的办法就是改变 right-bottom 的 z-index

  1. left 设置 z-index: 3
  2. right-header 设置 z-index: 2

  3. right-bottom 设置 z-index: 1

如下图,完美解决问题。

现在这个 dom 的层叠上下文如下:

  1. left 在根层叠上下文,z-index :3
  2. right-header 在根层叠上下文 z-index: 2
  3. right-bottom 在根层叠上下文 z-index: 1
  4. right-bottom-inner 在 right-bottom 创建的新层叠上下文中
    1. 即便设置了 z-index:999,层叠顺序也会被限制在父级 right-bottom 中
    2. 永远不会覆盖 left 和 right-header 

这就是我说的在项目的主要布局中的元素尽量都设置 z-index,等到项目很复杂的时候,如果项目中有很多绝对定位的弹框就不会层级关系搞不明白。

4.2 修改层级关系实践

理论知识太多不如一个实践,我们先看一下下面的代码:

<template>
	<div class="wrapper">
		<div class="left">
			<div class="left-header">菜单侧边栏</div>
			<div class="menu">
				<div class="menu-item">
					系统使用说明请看文档
					<a-tooltip placement="right" trigger="click">
						<template #title>菜鸟一定要认真看文档</template>
						<QuestionCircleOutlined />
					</a-tooltip>
				</div>
			</div>
		</div>
		<div class="right">
			<div class="right-header">
				菜鸟养成计划
				<QuestionCircleOutlined @click="showRightModal = true" />
			</div>
			<div class="right-bottom">
				<div class="inner-box">
					<div class="title">如何学好前端</div>
					<p class="content">关注我,我会持续更新高质量前端技术文档</p>
				</div>
				<div class="fix-btn" @click="showAdModal = true">广告</div>
			</div>
			<div v-if="showRightModal" class="right-fix-modal">
				<div class="close-btn" @click="showRightModal = false">关闭弹框</div>
				<p>好记性不如烂笔头</p>
				<p>站在岸上学不会游泳</p>
				<p>花盆里长不出参天松,庭院里练不出千里马</p>
			</div>
		</div>
	</div>
	<a-modal v-model:visible="showAdModal" title="特大喜讯">
		<p>关注我的博客成为前端大佬</p>
		<p>跟着我学习前端,太简单了</p>
		<p>绝对不会被AI取代的前端菜鸟</p>
	</a-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { Tooltip as ATooltip, Modal as AModal } from 'ant-design-vue'
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
const showAdModal = ref(false)
const showRightModal = ref(false)
</script>
<style lang="scss" scoped>
.wrapper {
	width: 100vw;
	height: 100vh;
	display: flex;
	align-items: stretch;
	justify-content: stretch;
	.left {
		display: flex;
		align-items: center;
		flex-direction: column;
		justify-content: space-between;
		width: 200px;
		color: #333;
		box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15);
		z-index: 20; // 侧边栏在最顶层
		background: #fafafa;
		.left-header {
			font-size: 20px;
			padding: 10px;
		}
		.menu {
			width: 100%;
			.menu-item {
				display: flex;
				align-items: center;
				justify-content: flex-start;
				margin: 5px 0;
				padding: 10px;
				gap: 5px;
				cursor: pointer;
				transition: background-color 0.3s;
				&:hover {
					background-color: #ddd;
				}
			}
		}
	}

	.right {
		display: flex;
		align-items: center;
		flex-direction: column;
		justify-content: stretch;
		flex-grow: 1;
		position: relative;
		.right-header {
			width: 100%;
			height: 64px;
			display: flex;
			align-items: center;
			justify-content: center;
			gap: 5px;
			box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.11);
			z-index: 5; // 右侧头部在上面,仅次于侧边栏
			span {
				cursor: pointer;
			}
		}
		.right-bottom {
			display: flex;
			align-items: center;
			flex-direction: column;
			flex-grow: 1;
			padding: 20px;
			width: 100%;
			.inner-box {
				display: flex;
				align-items: center;
				justify-content: center;
				flex-direction: column;
				width: 100%;
				margin-top: 100px;
				.title {
					font-size: 20px;
				}
				.content {
					margin-top: 10px;
					font-size: 18px;
					line-height: 20px;
				}
			}
			.fix-btn {
				display: flex;
				align-items: center;
				justify-content: center;
				position: fixed;
				bottom: 100px;
				right: 20px;
				width: 50px;
				height: 50px;
				background: red;
				border-radius: 50%;
				z-index: 2000;
				color: #fff;
			}
		}

		.right-fix-modal {
			display: flex;
			align-items: center;
			justify-content: center;
			position: absolute;
			width: 100%;
			height: 100%;
			top: 0;
			left: 0;
			background: #fff;
			z-index: 9999; // 为了让它在最上面,写了很大的 z-index
			.close-btn {
				border: 1px solid #ccc;
				position: absolute;
				display: flex;
				align-items: center;
				justify-content: center;
				padding: 2px 10px;
				border-radius: 2px;
				top: 10px;
				right: 10px;
				font-size: 12px;
				color: #666;
				cursor: pointer;
			}
		}
	}
}
</style>

4.2.1 两个bug

上面的代码有两个bug。

(1)bug 1: 点击右下角的【广告】按钮弹出弹框,但是按钮在弹框的蒙层上面,这是不对的,应该按钮也被蒙层覆盖。

(2)bug2: 点击右侧模块顶部的【菜鸟养成计划】文案旁边的 icon,打开一个右侧固定的弹框,再点击左侧侧边栏的菜单的 icon,发现提示文案被覆盖,这是不对的,侧边栏的 tooltip提示应该始终在最上面。

4.2.2 一行代码解决上述问题

给 right 增加 z-index : 10,只要是大于 left 的 z-index 值就行。

4.2.3 bug1 分析

bug1: 点击右下角的【广告】按钮弹出弹框,但是按钮在弹框的蒙层上面,这是不对的,应该按钮也被蒙层覆盖。

未修复前整个代码 dom 的层叠上下文如下:

  1. 根层叠上下文
    1. left 处于根层叠上下文,z-index: 20,并创建了新层叠上下文 #left
    2. right 处于根层叠上下文
      1. right-header 处于根层叠上下文,z-index: 5,并创建了新层叠上下文
      2. right-bottom 处于根层叠上下文
        1. fix-btn 处于根层叠上下文,z-index: 2000,并创建了新层叠上下文
    3. 弹框处于根层叠上下文,z-index: 1000【组件库默认的】,如下图

所以在根层叠上下文的对比中,fix-btn 的 z-index: 2000 大于弹框的 z-index: 1000,就导致弹框的蒙层被按钮覆盖了。

4.2.4 bug2 分析

点击画右侧模块顶部的【菜鸟养成计划】文案旁边的icon,打开一个右侧固定的弹框,再点击左侧侧边栏的菜单的icon,发现提示文案被覆盖,这是不对的,侧边栏的 tooltip 提示应该始终在最上面。

未修复前整个代码 dom 的层叠上下文如下:

  1.  根层叠上下文
    1. left 处于根层叠上下文,z-index: 20,并创建了新层叠上下文 #left
      1. menu 处于 #left 层叠上下文中,受限于 left
    2. right 处于根层叠上下文
      1. right-header 处于根层叠上下文,z-index: 5,并创建了新层叠上下文
      2. right-bottom 处于根层叠上下文
      3. right-fix-modal 处于根层叠上下文,z-index: 9999,并创建了新的层叠上下文
    3. menu 的提示 tooltip 处于于根层叠上下文,z-index: 1070【组件库默认的】,如下图

所以在根层叠上下文的对比中,right-fix-modal 的 z-index:9999 大于提示 tooltip 的 z-index: 1070,导致了 right-fix-modal 在顶层。

4.2.5 修复后的代码解释

我们给 right 增加了一个 z-index: 10,使 right 元素创建了新的层叠上下文,right 内部所有的元素的层级都受限于 right,包括 fix-btn 和 right-fix-modal。

此时,修复后整个代码 dom 的层叠上下文如下:

  1.  根层叠上下文
    1. left 处于根层叠上下文,z-index: 20,并创建了新层叠上下文 #left
      1. menu 处于 #left 层叠上下文中,受限于 left
    2. right 处于根层叠上下文,z-index: 10,并创建了新的层叠上下文 #right
      1. right-header 处于 #right 层叠上下文,z-index: 5,并创建了新层叠上下文
      2. right-bottom 处于 #right 层叠上下文
        1. fix-btn 处于 #right 层叠上下文,z-index: 2000,并创建了新的层叠上下文
      3. right-fix-modal 处于 #right 层叠上下文,z-index: 9999,并创建了新的层叠上下文
    3. bug1 中的弹框,处于根层叠上下文,z-index: 1000【组件库默认的】
    4. bug2 中的提示tooltip,处于根层叠上下文,z-index:1070【组件库默认的】

所以无论是 bug1 还是 bug2,弹框和 tooltip 提示都是在根层叠上下文中,right 中的元素都被封印在 right 创建的层叠上下文中了,永远不可能越过 right 的层级,就不会有上面的两个 bug 了。

这部分的代码在我的【learn-vite】这个项目的 z-index-demo 分支,可以直接克隆下来调试看看。注意使用 pnpm 安装依赖,关于这个项目的详细教程,请参考这篇文章。

一篇文章教小白学会搭建 vite + ts + vue3 项目,手把手教程,不会算我输(第一篇)_vite+vue+ts-优快云博客文章浏览阅读2.5w次,点赞64次,收藏223次。【前端工程化实践】手把手教小白搭建 vite + ts + vue3 项目_vite+vue+ts https://blog.youkuaiyun.com/qq_17335549/article/details/128480583

4.3 z-index 管理

    4.3.1 按照功能模块划分

    为了减少 bug,在大型项目中,可以参考组件库给每个功能模块设置一个z-index 区域,以下是 deepseek 给出的方案,仅供参考:

    定义明确的层级区间(示例):

    // 通过Sass/Less变量集中管理(推荐)
    $z-index: (
      deep:          -1,    // 背景元素
      default:        0,    // 普通元素
      content:       100,   // 主要内容区域
      sticky:        200,   // 吸附式元素
      header:        500,   // 顶部导航
      dropdown:      600,   // 下拉菜单
      overlay:       700,   // 遮罩层
      modal:         800,   // 弹窗
      notification:  900,   // 全局通知
      max:          9999    // 紧急临时层(慎用)
    );

    但是说实话我没有这样用过,因为很多项目我们都是用组件库默认的层级了,只要我们在布局的时候注意一下,基本上不需要手动修改组件库的 z-index,但是也需要注意了,不要随便使用 9999 这种特别高的层级。

    4.3.2 z-index 取值范围

    上面 1.3.3 我们说到 z-index 的取值也是有范围的,在实际项目应用中应该限制不要超过 4 位数,而且尽量不要用 9999 这种特别大的值。

    五、开发者工具 Layers 面板调试

    打开开发者工具  => layers 面板

      这个面板我之前也很少用到,学习完了层叠上下文相关的知识,让我们来一起研究一下。

      5.1 图层列表

      layers 面板的左侧是图层列表,显示所有合成层和层级关系,列表从上到下,就是从底层到顶层的排列关系。

      但是这个合成层和我们所说的层叠上下文不完全相同。比如说上图的源代码就是 4.2 中修复后的完整代码,但是我们之前说到该代码的 dom 层叠上下文的结构是这样的:

      1.  根层叠上下文
        1. left 处于根层叠上下文,z-index: 20,并创建了新层叠上下文 #left
          1. menu 处于 #left 层叠上下文中,受限于 left
        2. right 处于根层叠上下文,z-index: 10,并创建了新的层叠上下文 #right
          1. right-header 处于 #right 层叠上下文,z-index: 5,并创建了新层叠上下文
          2. right-bottom 处于 #right 层叠上下文
            1. fix-btn 处于 #right 层叠上下文,z-index: 2000,并创建了新的层叠上下文
          3. right-fix-modal 处于 #right 层叠上下文,z-index: 9999,并创建了新的层叠上下文
        3. bug1 中的弹框,处于根层叠上下文,z-index: 1000【组件库默认的】
        4. bug2 中的提示tooltip,处于根层叠上下文,z-index:1070【组件库默认的】

      在没有打开弹框的情况下,应该有根层叠上下文、#left 层叠上下文、#right 层叠上下文、#right-header 层叠上下文、#fix-btn 层叠上下文。

      但是在 layers 面板中我们只能看到两个项目,分别是【根层叠上下文  对应的  #document】、【#fix-btn 层叠上下文】。

      所以说创建层叠上下文不一定会创建新的图层,图层一般是于浏览器的硬件渲染逻辑相关的,但是我们也可以使用图层列表来观察我们页面的布局。

      如果我们点击【菜鸟养成计划】旁边的icon,打开右侧固定的弹框,会发现图层列表增加了一个图层。

      5.2 操作按钮

      5.1.2 移动

      还可以使用鼠标滚轮/触控板进行放大缩小。

      5.1.3 旋转

      5.1.4 重置

      5.1.5 展示/隐藏绘制效果

      5.1.6 展示滚动边界 show scroll reacts

      该选项用于可视化页面中所有可滚动区域的边界框,主要解决以下场景:

      1. 滚动区域重叠导致的渲染异常
      2. 滚动条位置偏移的定位
      3. 滚动性能优化时确认合成层状态
      4. 检查非预期的滚动容器

      我们可以找个复杂的网站看下效果,拿 element-plus 的官网来看。

      这个部分不同网站效果也不同,有的会多出一些粉色区块,有的是其他颜色的,总之这里面有更多知识点,我还没研究明白,等后续再更新。

      5.3 details 详情

      在面板下面有一个details 的选项卡,里面有一个【合成原因】可以看一下为啥会创建一个新的图层。

      除此之外 details 和 profiler 面板还有更多高端的的数据,在这篇文章先不深入研究了。

      主要是我也还没研究明白,hh~~

      六、总结

      这篇文章基本涵盖了 z-index 的使用方法和层叠上下文的概念,z-index 的使用在实际项目开发中应用很多,而层叠上下文的概念在面试的时候很多面试官会问到,可以用自己的话总结一下。理解层叠上下文的概念也有助于我们在实践中设计更合理的页面布局,找出一些莫名其妙的 bug。

      如果你认真的看完这篇文章,我相信你一定有提升,因为我写完之后已经感觉自己很了不起了(我飘了~)。

      最好是一边写例子、一边在浏览器上看效果、一边看文档,要不然还是记不住。

      感谢大家阅读,内容很多,难免疏漏,有不正确的地方欢迎评论区指出。同时也欢迎大家关注我的专栏,持续更新高质量文档。

      https://blog.youkuaiyun.com/qq_17335549/category_12388571.htmlhttps://blog.youkuaiyun.com/qq_17335549/category_12388571.html

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包

      打赏作者

      我有一棵树

      感谢支持

      ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
      扫码支付:¥1
      获取中
      扫码支付

      您的余额不足,请更换扫码支付或充值

      打赏作者

      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值