引言
你是否见过这样的代码?
css
.dialog {
z-index: 9999;
}
后来发现弹窗被挡住了:
css
.dialog {
z-index: 99999;
}
再后来:
css
.loading {
z-index: 999999;
}
最后项目里出现了这样一段祖传代码:
css
.customer-service {
z-index: 2147483647;
}
😅 此时团队里的每个人都知道这不对,但没人知道该怎么改。
本文将带你彻底搞懂 z-index,从基础原理到企业级落地方案。
🎯 一、z-index 到底是干嘛的?
很多人第一次接触 z-index 时,都会觉得:
"哦,这不就是控制谁盖住谁吗?"
没错,但只说对了一半。
🌍 浏览器其实是一个三维世界
我们平时看到的网页:
- X轴:左右
- Y轴:上下
但实际上浏览器还有:
- Z轴:前后
例如:
html
<div class="red"></div>
<div class="blue"></div>
css
.red {
position: absolute;
width: 200px;
height: 200px;
background: red;
z-index: 1;
}
.blue {
position: absolute;
left: 100px;
top: 100px;
width: 200px;
height: 200px;
background: blue;
z-index: 2;
}
效果:
text
蓝色块
↑
红色块
因为:
text
2 > 1
所以蓝色显示在红色上方。
这就是 z-index 最基础的能力:
✨ 决定元素的覆盖顺序。
🤔 二、大多数开发者是怎么使用 z-index 的
说实话。
绝大多数项目一开始都不是设计出来的。
而是"打出来的"。
第一版
css
.dialog {
z-index: 1000;
}
运行。
发现没问题。
开心下班。🍺
第二版
产品来了。
这个弹窗被顶部导航挡住了。
于是:
css
.dialog {
z-index: 9999;
}
问题解决。
继续下班。🍺
第三版
测试来了。
Toast 被弹窗挡住了。
于是:
css
.toast {
z-index: 10000;
}
第四版
运营来了。
活动弹窗必须显示最上面。
于是:
css
.activity {
z-index: 999999;
}
第五版
客服系统接入。
css
.customer-service {
z-index: 99999999;
}
🎉 恭喜。
你的项目正式进入:
text
魔法数字时代
⚠️ 三、为什么这种写法一定会出问题
因为这种思路的核心是:
text
谁挡我
↓
我写得比谁大
短期有效。
长期一定失控。
因为没人知道:
- 哪个数字还能用
- 哪个数字被占用了
- 哪个组件应该在哪一层
半年以后你会看到:
css
z-index: 88888888;
然后陷入沉思。
🤯
🔥 四、真正的大坑:Stacking Context(层叠上下文)
这里是 z-index 最容易翻车的地方。
也是面试最喜欢问的地方。
一个经典案例
html
<body>
<div class="parent">
<div class="child"></div>
</div>
<div class="mask"></div>
</body>
css
.parent {
position: relative;
z-index: 1;
}
.child {
position: absolute;
z-index: 999999;
}
.mask {
position: fixed;
z-index: 2;
}
很多人会认为:
text
999999 > 2
所以 child 应该最上面。
实际上不是。
结果是:
text
mask
↑
parent
↑
child
🏢 用楼房理解最简单
把层叠上下文理解成楼。
text
A楼(1层)
└── child(999999层)
B楼(2层)
└── mask(2层)
虽然 child 在 A 楼里住 999999 层。
但是整栋 A 楼都比 B 楼低。
所以:
text
B楼
永远压住
A楼
这就是为什么:
css
z-index: 99999999;
有时候依然没用。
💥 五、哪些属性会偷偷创建层叠上下文
最常见的几个:
transform
css
.card {
transform: translateZ(0);
}
很多人为了 GPU 加速写它。
结果顺手创造了一个新的世界。
🌚
opacity
css
.card {
opacity: 0.99;
}
也会创建新的层叠上下文。
filter
css
.card {
filter: blur(5px);
}
也会。
isolation
css
.card {
isolation: isolate;
}
也会。
😭 六、实际项目中最常见的问题
问题一:弹窗死活出不来
css
.page {
transform: translateZ(0);
}
然后:
html
<div class="page">
<Dialog />
</div>
你会发现:
css
z-index: 999999999;
都不一定有用。
问题二:多个弹窗顺序混乱
用户连续打开:
- Dialog
- Drawer
- Confirm
- ImagePreview
结果:
text
后开的
跑到了下面
用户直接懵了。
问题三:团队成员互相伤害
A:
css
9999
B:
css
10000
C:
css
999999
最后:
text
谁都不知道谁该在上面
🚀 七、现代框架怎么解决
Vue Teleport
Vue 官方方案:
vue
<template>
<Teleport to="body">
<div class="dialog">
我是弹窗
</div>
</Teleport>
</template>
核心思想:
text
不要待在父组件里
直接搬到 body
React Portal
React 官方方案:
js
import { createPortal } from 'react-dom'
export default function Dialog() {
return createPortal(
<div className="dialog">
Dialog
</div>,
document.body
)
}
思想完全一致。
🏆 八、知名组件库怎么做
Element Plus
思路非常简单。
维护一个全局变量:
js
let zIndex = 2000
export const nextZIndex = () => ++zIndex
打开一个弹窗:
text
2001
再打开一个:
text
2002
再打开一个:
text
2003
后开的永远压住前开的。
👍 简单粗暴。
Ant Design
Antd 采用固定层级。
text
Modal 1000
Message 1010
Dropdown 1050
Tooltip 1070
优点:
- 容易理解
- 容易维护
Material UI
很多人认为这是最成熟的设计。
js
const zIndex = {
appBar: 1100,
drawer: 1200,
modal: 1300,
snackbar: 1400,
tooltip: 1500
}
特点:
✅ 不允许乱写数字
✅ 统一管理
✅ Design Token 化
🏢 九、大厂通常怎么玩
真正的大厂很少允许这样:
css
z-index: 999999;
因为这意味着:
text
设计已经失控
他们会统一定义:
ts
export const Z_INDEX = {
HEADER: 100,
DROPDOWN: 200,
MODAL: 1000,
MESSAGE: 1100,
TOOLTIP: 1200
}
业务开发:
css
z-index: var(--z-modal);
而不是:
css
z-index: 88888888;
😂
👑 十、企业级最推荐方案
如果让我现在从零设计一个大型项目。
我会选择:
text
MUI分层思想
+
Design Token
+
Portal/Teleport
完整分层:
基础层:0~99
固定布局层:100~199
下拉层:200~299
Popover层:300~399
Modal层:1000~1099
Message层:1100~1199
Tooltip层:1200~1299
系统层:2000+
🎯 最后的结论
z-index 从来不是一个数字问题。
而是一个架构问题。
初级开发思考的是:
text
这个数字写多少?
高级开发思考的是:
text
这个组件属于哪个层级体系?
架构师思考的是:
text
如何让团队永远不需要关心数字?
当你的项目开始使用:
- Design Token
- Portal / Teleport
- 分层体系
- 统一规范
以后就不会再出现:
css
z-index: 131450232312;
这种祖传代码了。😎