z-index 层级管理体系:从入门到企业级架构实践

引言

你是否见过这样的代码?

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;

这种祖传代码了。😎