CSS 层叠上下文、层叠等级、层叠顺序与 z-index
Category(分类): CSS Status: 已整理
很多人第一次接触 z-index 时,会把它理解为“数值越大,元素越靠近观察者”。这个理解只在非常简单的场景下成立。
实际判断元素的覆盖关系,还需要考虑:
- 元素是否处于同一个层叠上下文;
- 元素所在层叠上下文的层叠等级;
- 元素在当前层叠上下文中的绘制顺序;
position、opacity、transform、filter、Flex/Grid 等是否创建了新的层叠上下文。
还要注意:层叠上下文不是合成层。层叠上下文是 CSS 绘制顺序概念,合成层是浏览器渲染实现中的图形优化概念,两者不能混为一谈。

一、什么是层叠上下文?
层叠上下文(stacking context)可以理解为一个独立的三维绘制环境。元素在页面中不仅有平面上的 X、Y 位置,还存在表示前后关系的 Z 轴。
层叠上下文具有以下特点:
- 它是父级层叠上下文中的一个原子化绘制单元;
- 它的后代只能在自己的上下文内部比较层叠关系;
- 后代不能通过设置非常大的
z-index越过父级层叠上下文; - 当前层叠上下文整体完成绘制后,再作为一个整体参与父级上下文的比较。
因此,层叠上下文不代表元素一定更靠上。一个 z-index: -1 的层叠上下文可能位于普通内容下方。
二、什么是层叠等级?
层叠等级(stacking level)表示盒子在当前层叠上下文中的绘制层级。它不是一个可以脱离上下文全局比较的数字。
例如:
.box-a {
position: relative;
z-index: 2;
}
.box-b {
position: relative;
z-index: 1;
}
.box-b .child {
position: absolute;
z-index: 999999;
}
如果 .box-a 和 .box-b 都创建了层叠上下文,那么 .box-b .child 的 999999 只能在 .box-b 内部比较,不能越过 .box-b 所在的层叠上下文去覆盖 .box-a。
判断顺序应当是:
- 找到两个目标元素最近的层叠上下文祖先;
- 比较它们在共同父级层叠上下文中的等级;
- 如果属于同一个层叠上下文,再比较各自在该上下文中的绘制顺序和
z-index; - 如果所有条件都相同,通常由 DOM 顺序决定后绘制者覆盖先绘制者。
三、如何创建层叠上下文?
常见情况包括:
- 根元素
<html>创建根层叠上下文; - 定位元素(
relative、absolute等)设置非auto的z-index; position: fixed元素通常创建层叠上下文;position: sticky元素创建层叠上下文;- Flex 项目或 Grid 项目设置非
auto的z-index; opacity的计算值小于1;transform、scale、rotate或translate不为默认值;filter或backdrop-filter不为none;perspective不为none;mix-blend-mode不为normal;isolation: isolate;clip-path、mask 等部分图形效果;- 某些
contain值,例如contain: paint; will-change指定了一个本身会创建层叠上下文的属性;container-type: size或inline-size;- 顶层(top layer)中的元素,例如部分
dialog和 Popover。
-webkit-overflow-scrolling: touch 属于历史 WebKit 行为,不建议作为现代 CSS 的通用层叠上下文规则。
四、示例:没有父级层叠上下文时
下面有两个 div,三个绝对定位的段落:
<div>
<p class="a">a</p>
<p class="b">b</p>
</div>
<div>
<p class="c">c</p>
</div>
div {
position: relative;
width: 100px;
height: 100px;
}
p {
position: absolute;
width: 100px;
height: 100px;
margin: 0;
font-size: 20px;
}
.a {
z-index: 1;
background: blue;
}
.b {
z-index: 2;
top: 20px;
left: 20px;
background: green;
}
.c {
z-index: 3;
top: -20px;
left: 40px;
background: red;
}
父级 div 虽然设置了 position: relative,但没有设置非 auto 的 z-index,因此它们不会因为这条声明创建新的层叠上下文。三个 p 都可以在根层叠上下文中比较,z-index: 3 的 .c 通常位于最上层。

五、示例:父级创建层叠上下文后
<div class="box1">
<p class="a">a</p>
<p class="b">b</p>
</div>
<div class="box2">
<p class="c">c</p>
</div>
.box1,
.box2 {
position: relative;
width: 100px;
height: 100px;
}
.box1 {
z-index: 2;
}
.box2 {
z-index: 1;
}
p {
position: absolute;
width: 100px;
height: 100px;
margin: 0;
}
.a {
z-index: 100;
background: blue;
}
.b {
z-index: 200;
top: 20px;
left: 20px;
background: green;
}
.c {
z-index: 9999;
top: -20px;
left: 40px;
background: red;
}
.box1 和 .box2 都创建了层叠上下文:
.box1的层叠等级是2;.box2的层叠等级是1;.c的9999只能在.box2内部比较;.box2整体低于.box1,因此.c不能覆盖.box1中的内容。

六、什么是层叠顺序?
层叠顺序(stacking order)是浏览器在同一个层叠上下文中绘制元素时遵循的规则。下面是简化后的常见绘制顺序,从后到前:
- 层叠上下文元素的背景和边框;
- 层叠等级为负数的后代层叠上下文;
- 普通流中的非定位块级后代;
- 浮动后代;
- 普通流中的行内级后代;
z-index: auto或z-index: 0的定位后代及层叠上下文;- 正数层叠等级的后代层叠上下文。
这是为了帮助理解的简化模型。现代 CSS 还涉及 Flex/Grid 项目、伪元素、顶层元素、容器、混合模式和其他新的绘制规则,具体结果应结合规范和浏览器开发者工具判断。
inline、inline-block、block 和 float 的顺序
原文称行内元素层叠在块级和浮动元素上方,并把原因解释为“文字内容更重要”。实际原因是 CSS 规定的绘制顺序,而不是浏览器对文字内容的价值判断。
当多个元素处于同一层叠上下文且没有更高层级的定位规则时,行内、块级和浮动元素会按照规范规定的绘制阶段参与比较。

z-index: auto 和 z-index: 0
这两个值不能简单认为完全相同:
.auto-box {
position: relative;
z-index: auto;
}
.zero-box {
position: relative;
z-index: 0;
}
通常:
z-index: auto不会因为定位而创建新的层叠上下文;z-index: 0会让定位元素创建新的层叠上下文;- 两者可能处于相近的绘制阶段,但后代的层叠范围不同。
七、DOM 顺序与 z-index
先看一个 z-index: auto 的例子:
<div class="box1">
<div class="child1"></div>
</div>
<div class="box2">
<div class="child2"></div>
</div>
.box1,
.box2 {
position: relative;
z-index: auto;
}
.child1,
.child2 {
position: absolute;
top: 0;
left: 0;
}
.child1 {
z-index: 2;
width: 200px;
height: 100px;
background: #168bf5;
}
.child2 {
z-index: 1;
width: 100px;
height: 200px;
background: #32c292;
}
由于 .box1 和 .box2 的 z-index 都是 auto,它们不会创建新的层叠上下文,两个子元素可以在根层叠上下文中比较。此时 .child1 的 z-index: 2 高于 .child2 的 1。

如果把父级改成:
.box1,
.box2 {
position: relative;
z-index: 0;
}
两个父级都会创建层叠上下文,并且它们的层叠等级相同。由于 .box2 在 DOM 中位于后面,.box2 整体通常会绘制在 .box1 之上,子元素自身的 z-index 不能跨越父级上下文。

八、Flex/Grid 项目中的 z-index
z-index 不只适用于设置了 position 的元素。Flex 项目和 Grid 项目即使没有设置定位,也可以使用非 auto 的 z-index 创建层叠上下文:
<div class="flex-box">
<div class="parent">
parent
<div class="child">child</div>
</div>
</div>
.flex-box {
display: flex;
}
.parent {
z-index: 1;
width: 200px;
height: 100px;
background: #168bf5;
}
.child {
position: relative;
z-index: -1;
width: 100px;
height: 200px;
background: #32d19c;
}
.parent 是 Flex 项目,虽然没有设置 position,但 z-index: 1 仍然可以使它创建层叠上下文。这个结果与普通块级元素直接设置 z-index: 1 的情况不同。

同理,Grid 项目也可以使用 z-index:
.grid {
display: grid;
}
.grid-item {
z-index: 1;
}

九、层叠上下文的排查方法
遇到“z-index 很大但仍然被覆盖”的问题,可以按以下顺序排查:
1. 检查目标元素的祖先
查看目标元素以及所有祖先是否设置了:
position
z-index
transform
opacity
filter
isolation
contain
will-change
任何一个祖先创建了层叠上下文,都可能限制子元素的层叠范围。
2. 提升正确的层级
如果弹窗被另一个模块覆盖,通常应该提高弹窗所在父级层叠上下文的等级,而不是无限增大弹窗子元素的 z-index:
.page-content {
position: relative;
z-index: 1;
}
.modal-layer {
position: relative;
z-index: 100;
}
.modal {
position: absolute;
z-index: 1;
}
3. 检查无意创建层叠上下文的属性
以下写法经常让开发者误以为只是视觉效果:
.wrapper {
transform: translateZ(0);
opacity: 0.999;
filter: blur(0);
isolation: isolate;
}
它们可能改变层叠关系。应在不需要时移除,或调整父级层级。
4. 使用开发者工具
现代浏览器的 Elements、Computed 和 Layers 面板可以帮助检查:
- 哪些元素创建了层叠上下文;
- 元素当前的
z-index; - 元素是否位于 top layer;
- 哪个祖先限制了绘制顺序。
十、常见误区
误区一:z-index: 999999 可以解决所有覆盖问题
不能。子元素无法越过父级层叠上下文。
误区二:设置 position: relative 就一定创建层叠上下文
不一定。通常需要配合非 auto 的 z-index,但 fixed、sticky 和其他触发条件是例外。
误区三:所有 z-index 都必须配合 position
Flex 项目和 Grid 项目可以在没有 position 的情况下使用 z-index。
误区四:层叠上下文就是合成层
不是。层叠上下文是 CSS 绘制顺序,合成层是浏览器渲染实现中的图形层概念。
误区五:DOM 后面的元素一定覆盖前面的元素
只有在相同层叠上下文、相同绘制阶段和相同层级条件下,DOM 顺序才通常决定前后关系。
总结
z-index不能脱离层叠上下文直接比较;z-index通常适用于定位元素,也适用于 Flex/Grid 项目;- 层叠上下文是父级上下文中的原子绘制单元;
- 子元素不能通过更大的
z-index越过父级层叠上下文; z-index: auto和z-index: 0的层叠行为不同;position: fixed、sticky、opacity、transform、filter、isolation、contain等都可能创建层叠上下文;- 层叠顺序来自 CSS 绘制规则,不是浏览器对文字重要性的判断;
- DOM 顺序只有在同一层叠上下文和相同绘制条件下才通常决定覆盖关系;
- 排查覆盖问题时,应先检查祖先层叠上下文,再调整正确的父级层级;
- 层叠上下文、BFC 和合成层是不同概念,需要分别理解。