z-index生效需同属一个堆叠上下文,否则值再大也无效;常见触发条件包括position非static加z-index、opacity<1、transform/filter非none等;表单元素和iframe因窗口特性无视z-index。

分组属性本身不控制显示优先级——z-index 才是直接生效的机制,但它的生效前提依赖于元素是否处于同一“堆叠上下文”,而这个上下文往往由 position、display、opacity 等属性隐式创建。
z-index 不生效?先检查是否在同一个堆叠上下文中
很多人给两个 div 都设了 z-index: 10 和 z-index: 20,却发现后者没盖住前者。根本原因不是值写错了,而是它们压根不在同一个堆叠上下文里。
- 每个新堆叠上下文会重置 z-index 的“计数起点”:父容器设了
position: relative且z-index: 1,它的子元素即使设z-index: 999,也只在这个父容器内部有效,无法越过父容器去盖住页面其他区域的元素 - 常见触发新堆叠上下文的属性包括:
position(非static)+z-index(非auto)、opacity小于 1、transform非none、filter非none、will-change指定相关属性 - 浏览器 DevTools 的“Layers”面板(Chrome)或“Computed”中查看
stacking context字段,能快速确认当前元素是否已脱离默认上下文
表单元素(如 select)为什么总在最上层?
这不是 CSS 控制的结果,而是浏览器渲染引擎的底层限制:select、iframe、object、Flash 插件等属于“有窗口元素(Windowed Element)”,它们天然位于所有“无窗口元素(Windowless Element)”之上,z-index 完全无效。
- 这种行为在所有主流浏览器中一致,不是 bug,是历史兼容性设计
- 想让弹层盖住
select?唯一可靠办法是临时用visibility: hidden或display: none隐藏它;或者改用纯 DOM 实现的下拉组件(如div+ul)替代原生select -
iframe同理:即使父页面设置了z-index: 9999,也无法压住同域或跨域的iframe内容,除非该iframe自身设置了pointer-events: none且你用遮罩层盖在其上
display 值切换会意外破坏 z-index 层级关系
把一个原本 position: absolute + z-index: 5 的元素,临时改成 display: none,再切回 display: block,它不会“恢复”到原来层级——因为 display: none 让它彻底退出渲染树,重建时会按当前文档流位置和新堆叠上下文重新计算。
立即学习“前端免费学习笔记(深入)”;
-
visibility: hidden是更安全的隐藏方式:元素保留在渲染树中,尺寸、位置、层级关系全部维持,只是不绘制像素 - 如果必须用
display切换,记得同步重置position和z-index,尤其当父容器也是动态创建/销毁时 -
display: flex或display: grid容器内的子项,若同时用了position: absolute,其z-index只在该 flex/grid 容器内生效,不能跨容器竞争层级
真正容易被忽略的是:堆叠上下文不是靠“分组属性”建立的,而是靠具体样式声明触发的。你以为加了个 class="dropdown-group" 就能统一控制层级,其实起作用的是这个 class 里写的 position: relative 和 z-index: 100 ——删掉这两行,再多的分组语义也毫无意义。


















