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