z-index 写了 9999 还是被盖住:层叠上下文一次讲透

z-index 无效的根因从来不是数值不够大,而是层叠上下文:z-index 只在同一个上下文里比较,transform、opacity、filter、position 都会凭空创建新上下文,把你的 9999 关进局部牢笼。本文给出层叠顺序七层图、常见「隐形上下文」清单、以及三步定位法。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

核心规则:z-index 只在「同一个层叠上下文」里比较

浏览器不是把全页所有元素的 z-index 拉通排序,而是先按「上下文」分组:每个层叠上下文是一个独立考场,内部成员的层级只在考场内比,整场考试的最终位置由创建该考场的元素在外层考场里的位置决定。所以子元素的 z-index: 9999 永远逃不出父级上下文的「天花板」——父级在外层只排到 1,子级内部再大也只在父级的框里画。

9999 被关进局部考场
.overlay  { z-index: 1; }                 /* 外层考场:它在第 1 层 */
.overlay .tip { z-index: 9999; }            /* 考场内再大也只是「第 1 层里的最上面 */
.sibling  { z-index: 2; position: relative; }  /* 外层第 2 层 → 盖住整个 overlay 考场 */

绘制顺序七层图:同上下文里谁在上面

同一个层叠上下文内,绘制顺序固定为七层,后面的盖前面的:背景边框 → 负 z-index 子上下文 → 块级盒 → 浮动盒 → 行内盒 → z-index: 0 / auto 的定位元素 → 正 z-index 子上下文。这就是「没有 z-index 时图片盖住文字(行内盒在块级盒之后)」「负 z-index 会沉到背景之上、内容之下」的原因。

「隐形上下文」清单:这些属性都在偷偷创建

position: fixed/sticky(几乎 always)、transform、perspective、filter、backdrop-filter、opacity 小于 1、mix-blend-mode、isolation: isolate、will-change 指定上述属性、contain: layout/paint——都会为元素创建层叠上下文。所谓「加了 transform 动画后 z-index 全乱了」,就是动画开始的一瞬间元素从「无上下文」变成「新上下文」,整个子树被整体锁进了新的考场。

属性触发条件典型翻车场景
transform任何非 none 值入场动画开始瞬间层级重排
opacity< 1淡入动画时下拉菜单被盖住
filter / backdrop-filter非 none毛玻璃卡片内的弹层被外部盖住
position: fixed / sticky存在即创建fixed 头部内的下拉被内容盖住
will-change指定 transform 等「只是声明优化」却改了层级
isolation: isolate存在即创建混合模式隔离时顺带改了层级

三步定位法

  1. 用 DevTools 选中「被盖住」的元素,向上找它所有的定位/变换祖先,确认它其实活在哪个上下文里——被盖住多半意味着它所在的考场整体层级低于对方考场。
  2. 比较两个「考场创建者」之间的 z-index(不是被盖元素和遮挡元素),在考场创建者身上改数值。给内部子元素狂加 z-index 是无效药。
  3. 如果两个考场创建者没有共同的定位祖先可比(各自在不同父级里),就逐级向上找第一个有共同父级的层级对,在那一级决定谁上谁下;必要时用 isolation: isolate 显式圈出考场,把层级关系写清楚而不是靠数值堆大。

团队实践:让 z-index 可维护

  • 建立分层常量(dropdown: 100、sticky-header: 200、modal: 1000、toast: 2000),全站只从这套常量取值,不再出现 9999、99999 的军备竞赛
  • 每个会创建上下文的属性(transform/opacity/filter)写注释标明「此处创建层叠上下文」,评审时优先检查这些位置
  • 浮层组件统一挂在 body 直下的 portal 容器里,让所有浮层在同一个考场比较,而不是散落在各自组件树深处
  • 能用 DOM 顺序解决的(后写的 DOM 在上)就不要用 z-index

常见问题

z-index 写多大都没用,第一件事查什么?

查它的父级链上有没有非 static 定位或 transform/opacity/filter 等属性——它们创建了层叠上下文,把元素锁在局部考场里。要比较的其实是考场创建者之间的层级。

为什么 transform 一开始动画层级就乱?

transform 非 none 即创建层叠上下文:动画开始的一瞬间元素从「普通盒」变成「新考场」,它整个子树的层级被锁进这个考场内部,与外部元素的相对关系随之改变。

z-index 有最大值吗?

规范上是 32 位有符号整数(2147483647),浏览器会把更大的值钳到该上限。依赖极限数值本身就是坏味道,应改用分层常量管理。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件