z-index不生效主因是元素未定位或被父层叠上下文隔离;需确认position非static,并逐级检查opacity<1、transform非none等触发stacking context的祖先元素。

z-index 不生效,先看元素有没有定位
直接写 z-index: 999 却没用,八成是因为元素还是 position: static(默认值)。浏览器会完全忽略这个声明,元素照旧按 DOM 顺序渲染——后出现的盖住先出现的,和你写的数字无关。
必须显式设置 position:哪怕只是 position: relative,只要不是 static,z-index 才开始起作用。
- 检查 computed styles 里
position的实际值,别只信源码里写了什么 -
position: sticky也支持z-index,但只在粘性生效区域内有效 - 用
transform、opacity: 0.99、filter等属性会悄悄创建新层叠上下文,导致子元素的z-index只在内部比大小
父容器静默创建了层叠上下文
这是最隐蔽也最常被忽略的卡点:某个中间父级没写 z-index,但用了 transform: translateZ(0) 或 will-change: transform,它就自动成了新的层叠上下文根节点。按钮再怎么设 z-index: 9999,也只能在那个父容器内部“称王”,出不去。
打开开发者工具 → Elements 面板 → 选中遮挡物或被遮挡元素 → 右侧 Styles 标签页往下拉,找 “Stacking Context” 字样。如果写着 “Yes”,说明它就是层级隔离的源头。
立即学习“前端免费学习笔记(深入)”;
- 常见触发条件:
transform、opacity < 1、filter、perspective、isolation: isolate - 解决思路:要么把触发属性移到更下层子元素上,要么给该父容器也加
position: relative和合理z-index,让它参与外层竞争 - Vue/React 组件里尤其要注意 CSS-in-JS 库(如 Emotion、styled-components)可能自动注入
will-change
伪元素或动画装饰层意外置顶
::before、::after 或带 @keyframes 的滚动文字,常因未设 z-index 默认跑到最顶层,把按钮整个盖住。它们一旦加了 position: absolute,又没配 z-index,浏览器会按“后来者居上”规则排,默认高于兄弟元素。
给伪元素加 z-index: 0 就能压回去——前提是它已定位(position: absolute 或 relative)。
- 避免用
z-index: -1:部分 iOS Safari 和 Android WebView 会让元素彻底失焦,点击无效 - 全屏类伪元素(如
body::before)必须确保目标按钮所在容器有更高z-index且自身已定位 - 滚动文字容器建议加
z-index: 2,按钮设z-index: 3,留出余量
组件化框架里层级被重置
Vue 的 v-if 或 React 的 useState 控制显隐时,弹窗按钮点不了,往往不是按钮本身问题,而是外层容器没撑起层叠上下文。每次条件渲染,DOM 节点重建,父级的 z-index 上下文可能丢失。
弹窗最外层容器(比如 .modal)必须带 position: fixed 和明确 z-index;不要把 z-index 写在 v-if 控制的子节点上。
- 错误写法:
<div v-if="show"><button style="z-index: 100">确认</button></div> - 正确写法:
<div class="modal" v-if="show"><button>确认</button></div>,.modal { position: fixed; z-index: 100; } - 若用 Portal 挂载到
body下,仍需确保 Portal 容器有position+z-index,否则它会被body的默认层叠上下文压制
opacity: 0.99 或 transform 把整个子树锁进了独立的层级牢笼里。查 “Stacking Context” 比盲调 z-index 快十倍。



















