理解 CSS 层叠上下文与 z-index — 从一个真实案例出发
一、问题背景
在布局中,我们常使用 z-index 来控制元素的前后层级。
但很多前端开发者都会遇到这样令人困惑的情况:
“我明明给子元素设置了更高的 z-index,为什么还是被盖住了?”
例如以下代码:
<template><div class="bigBox"><div class="childBox1">1<div class="child1B">11</div></div><div class="childBox2">2<div class="child2B">22</div></div></div>
</template>
.bigBox {position: relative;width: 1000px;height: 600px;border: 1px solid #000;margin: 0 auto;.childBox1 {position: absolute;top: 0;left: 0;width: 500px;height: 600px;background-color: skyblue;z-index: 40;.child1B {position: absolute;top: 50%;right: -150px;width: 100px;height: 100px;background-color: orange;z-index: 1050;}}.childBox2 {position: absolute;top: 0;right: 0;width: 500px;height: 600px;background-color: pink;z-index: 40;.child2B {position: absolute;top: 50%;left: -10px;width: 100px;height: 100px;background-color: greenyellow;z-index: 100;}}
}
你可能会预期橙色 .child1B 会浮在绿色 .child2B 上方。
但实际上,它被挡住了。
二、核心概念:层叠上下文(Stacking Context)
1. 什么是层叠上下文?
层叠上下文是一个 独立的渲染空间,在其中的元素会按照自身的 z-index 排列。
不同层叠上下文之间的元素不会互相干扰。
换句话说:
“每个层叠上下文内部的元素,只能在自己的上下文里比较 z-index。”
当你为一个元素设置了以下任意属性,它就会创建一个新的层叠上下文:
| 创建条件 | 示例 |
|---|---|
| 根元素(html) | 页面天然有一个全局层叠上下文 |
position 不为 static 且有 z-index | position: relative; z-index: 1 |
| 元素透明度 < 1 | opacity: 0.9 |
filter, transform, clip-path 等 | transform: translate(0) |
will-change、mix-blend-mode 等 | will-change: transform |
三、你的代码为什么会出现问题
在你的代码中:
.childBox1有position: absolute且z-index: 40
→ 创建了一个层叠上下文.childBox2同样也创建了一个层叠上下文
于是 .child1B 和 .child2B 分别存在于两个 独立的层叠上下文 中。
层级结构如下:
.bigBox(根上下文)
├─ .childBox1(z-index: 40)
│ └─ .child1B(z-index: 1050) ← 只在这里生效
└─ .childBox2(z-index: 40)└─ .child2B(z-index: 100)
当两个父盒子 (childBox1 和 childBox2) 的 z-index 相同时:
它们的绘制顺序由 DOM 顺序决定;
.childBox2写在后面,因此会在上层;.child1B再高的 z-index 也无法跨越父级的层叠上下文;最终橙色的
.child1B被粉色.childBox2盖住。
四、解决方案
✅ 方案 1:提升父层的 z-index
让 .childBox1 的 z-index 高于 .childBox2:
.childBox1 {z-index: 50;
}
这样整个 .childBox1(以及里面的 .child1B)都会显示在 .childBox2 之上。
✅ 方案 2:让子元素脱离父层叠上下文
如果不希望受父级 z-index 限制,可以让 .child1B 不在 .childBox1 的上下文中,比如:
.child1B {position: fixed;z-index: 1050;
}
或直接放到 .bigBox 层级下:
<div class="child1B">11</div>
✅ 方案 3:让父级不创建层叠上下文
移除父元素的 z-index:
.childBox1 {position: absolute;/* 不写 z-index */
}
此时 .child1B 和 .child2B 将在同一层叠上下文内比较 z-index。
五、z-index 的优先级规则(可打印版)
| 优先级 | 类型 | 说明 |
|---|---|---|
| 最高 | 新的层叠上下文的 z-index 值 | 父层级优先级高的整个上下文在上面 |
| 中 | 同层级的 DOM 顺序 | 相同 z-index 时,后写的元素在上面 |
| 内部比较 | 同一层叠上下文内的 z-index | 仅在当前上下文有效 |
六、总结与最佳实践
| 建议 | 说明 |
|---|---|
| ❌ 不要滥用 z-index | 过多层叠上下文会导致维护困难 |
| ✅ 尽量少创建层叠上下文 | 仅在需要隔离层级时使用 |
| ✅ 使用层级规划表 | 团队项目中定义:弹窗 > 遮罩 > 内容 > 底图 |
| ✅ 检查父级 z-index | 子元素“被挡住”的 90% 原因是父级 z-index 太低 |
| ✅ 使用浏览器 DevTools | Chrome → “Layers” 面板可视化层叠结构 |
七、结语
z-index 并不是一个“简单的数字比较”,而是一种“分层的空间体系”。
理解 层叠上下文 (stacking context),才能真正掌控元素的层级表现。
一句话总结:
z-index 决定了层内顺序,层叠上下文决定了游戏规则。
