z-index无效主因是父元素创建了堆叠上下文,使子元素z-index仅在内部生效;需检查opacity

z-index 写了却没用,八成不是数值太小,而是你正被某个父元素悄悄创建的堆叠上下文(stacking context)困住了。
怎么快速确认是不是堆叠上下文在捣鬼
别猜,直接看浏览器怎么算的:
- 打开 Chrome DevTools,选中目标元素,在右侧面板切换到 Layout 标签页,留意「Stacking context」是否显示为
Yes - 或者打开 More Tools → Layers 面板,观察渲染树里有没有意外出现的分组边界——那些就是独立堆叠上下文的“墙”
- 在 Computed 面板搜
z-index,如果显示为auto或灰显,说明它根本没参与比较,大概率被锁在某个上下文里了
哪些样式会无声无息地创建堆叠上下文
这些属性看着只是加点透明、动一下、模糊一下,实则一加就“结界成型”:
-
opacity小于 1(哪怕opacity: 0.999) -
transform不是none(包括transform: translateZ(0)、scale(1)、rotate(0)) -
filter(哪怕filter: blur(0)或filter: opacity(1)) -
will-change: transform或will-change: opacity isolation: isolate-
position: fixed或sticky且z-index不是auto - flex / grid 容器中,子项设置了
z-index(非auto)也会触发
特别注意:z-index: 0 和 z-index: auto 效果不同——前者只要元素已定位,就会创建新上下文;后者不会。别以为加个 0 很安全,它可能正是问题源头。
为什么子元素 z-index 再大也盖不住兄弟元素
两个 position: absolute 元素,一个设了 z-index: 9999,另一个是 z-index: 1,结果后者反而盖在上面。这不是 bug,是它们根本不在同一个比较场里:
- 前者父容器是普通
<div>(无堆叠上下文),所在上下文默认层级是 0 <li>后者父容器加了 <code>opacity: 0.99(触发新上下文),而这个父容器自身的z-index是 10 - 于是后者的整个“盒子”都以 10 层级参与全局比大小,内部再高的
z-index都出不去 - 临时注释掉可疑父级的
opacity、transform或filter,看遮挡是否立刻消失——如果恢复了,问题就定位准了 - 必须保留这些效果时,给该父容器本身加
position: relative+ 合理的z-index(比如z-index: 1000),而不是只给它的子元素狂堆数字 - 在 Vue/React 中,避免把
modal套在带transform的布局容器里;改用createPortal直接挂到body下,物理脱离干扰上下文
所以真正要调的,往往不是子元素的 z-index,而是那个触发上下文的父容器本身的 z-index 值。
临时验证和长期规避的关键动作
遇到遮挡异常,先做两件事:
堆叠上下文不是错误,是 CSS 渲染模型的固有机制。问题出在“不知道它存在”,而不是它不该存在。











