z-index 默认值是 auto 而非 0,auto 不提升层叠等级,仅脱离文档流;需显式设置 z-index 才能参与层叠竞争,且须注意父容器是否意外创建层叠上下文。

定位元素默认 z-index 是 auto,不是 0
很多人以为 position: relative 一加,z-index 就自动变成 0,其实不是。z-index: auto 是一个特殊值,它会让元素在层叠顺序中“退居二线”——哪怕它脱离了文档流,也会被某些普通流元素(尤其是后出现的块级元素)视觉上盖住。
常见错误现象:给弹窗加了 position: absolute,但页面底部一个没设定位的 <div> 却把它挡住了;或者下拉菜单展开后,被后面一个普通 <code><section></section> 盖掉。
-
z-index: auto的定位元素,在层叠等级上等同于“未参与定位竞争”的普通流内容,只是绘制时机靠前而已 - 真正起作用的是“层叠上下文中的 stack level”,
auto值不创建新上下文,也不提升自身层级 - 如果父容器有
opacity: 0.99或transform: scale(1),那子元素哪怕设了z-index: 999,也只在这个父上下文内有效
浮动元素和普通块级元素的渲染顺序干扰定位元素
浮动元素(float: left)本身不创建层叠上下文,也不响应 z-index,但它在浏览器绘制流水线里会被提前处理——先画浮动盒(含背景、边框),再画后续普通流块。如果这些普通流块没设背景色,就会“透出”浮动元素的底色,造成“浮动压定位”的错觉。
更麻烦的是:当定位元素的父容器是普通流,而浮动元素在 HTML 中写在它后面,且两者都没显式 z-index,那么浮动元素可能视觉上覆盖定位元素——这不是层级高,而是绘制顺序 + 背景缺失 + 文档流位置共同导致的。
- 检查浮动元素是否出现在定位元素之后的 HTML 顺序中
- 给定位元素的父容器加
position: relative和z-index: 1,强制它进入更高 stack level - 避免用浮动做布局主干;现代场景优先用
display: flex或grid,它们的子元素天然支持z-index
父容器意外创建了隔离的层叠上下文
只要父元素满足以下任一条件,就会创建新的层叠上下文,把子元素的 z-index “锁死”在里面:opacity 小于 1、transform 非 none、filter 有值、will-change 指定属性、或 position + 非 auto z-index。
这时即使子元素设了 z-index: 9999,它在整个页面里的实际层级,也只取决于它父容器在外部上下文中的 stack level。结果就是:定位子元素再努力,也盖不过外部一个 z-index: 2 的兄弟容器。
- 用浏览器 DevTools 的 Layers 面板(Chrome 中开启 “Rendering → Show layer borders”)确认真实层叠结构
- 排查祖先链中第一个设置了
z-index且值不为auto的定位元素 - 把
opacity: 0.99改成opacity: 1,或把transform: translateZ(0)换成will-change: auto,常能立刻修复
未定位元素靠文档流顺序“后来者居上”
两个都未设 position 的块级元素,后写的那个自然盖在前面那个上面——这是 CSS 层叠顺序的基本规则。但如果其中一个悄悄加了 position: relative 却没配 z-index,它反而可能被后面没定位的元素盖住,因为 z-index: auto 不提升层级,只让它“脱离流但不争层叠”。
这和很多人直觉相反:以为“定位=往上浮”,其实定位只是“挪位置”,不等于“升层级”。要升层级,必须显式干预 stack level。
- 所有参与遮盖竞争的元素,至少得有一个显式
z-index(哪怕只是1) - 不要依赖 HTML 顺序控制遮盖关系;它脆弱、不可控、跨浏览器表现不一致
- 最稳妥的做法:给需要“浮在最上”的组件(如 toast、modal、dropdown)统一加
position: fixed或absolute+z-index: 1000+,并确保其父容器没触发意外层叠上下文
真正难调的从来不是单个 z-index 数值,而是搞清当前元素到底属于哪个层叠上下文、它的父辈有没有悄悄把它“关进小黑屋”。每次遮盖异常,先打开 Layers 面板看真实分层,比反复改数字高效得多。











