问题现象
在开发中,我们可能遇到这样的情况:弹窗的 z-index 已经设为 9999,却依然被页面上的某些元素遮挡。这通常是因为层叠上下文在作祟。
什么是层叠上下文?
层叠上下文(Stacking Context)是 HTML 元素在渲染时的一个三维概念,它决定了元素在 Z 轴上的排列顺序。每个层叠上下文内部,子元素的 z-index 只在该上下文内比较,不同上下文之间的层级由父级上下文决定。
如何创建层叠上下文?
以下常见属性会创建新的层叠上下文(MDN 标准):
- 根元素
<html> position: absolute/relative且z-index不为autoposition: fixed或stickyflex容器的子元素且z-index不为autogrid容器的子元素且z-index不为autoopacity小于1transform、filter、perspective、clip-path、mask等不为noneisolation: isolatewill-change指定了上述任意属性contain: layout或paint
为什么 z-index 失效?
当弹窗的祖先元素创建了层叠上下文,且该上下文的层级低于遮挡元素的上下文时,弹窗的 z-index 再大也无法突破父级的限制。例如:
<div style="position: relative; z-index: 1;">
<div class="modal" style="position: fixed; z-index: 9999;">弹窗</div>
</div>
<div style="position: relative; z-index: 2;">遮挡元素</div>
弹窗的父级 z-index: 1 低于遮挡元素的 z-index: 2,因此弹窗永远在遮挡元素下方。
解决方案
1. 将弹窗移到 body 下
使用 ReactDOM.createPortal(React)或 Vue Teleport(Vue 3)将弹窗渲染到 body 末尾,避免受祖先层叠上下文影响。
2. 调整祖先的层叠顺序
确保弹窗所在的层叠上下文层级高于遮挡元素。可以提升祖先的 z-index 或移除不必要的层叠上下文属性。
3. 使用 isolation 隔离
如果只是想限制内部层级不影响外部,可以给容器添加 isolation: isolate,但注意这也会创建新的层叠上下文。
4. 避免滥用 z-index
不要盲目设置 z-index: 9999,而是理解层级关系,用合理的数值管理(如 CSS 变量定义层级)。
调试技巧
- 在浏览器开发者工具中,选中元素,查看“Layers”面板(Chrome)或“3D 视图”查看层叠情况。
- 使用
z-index调试工具(如z-index可视化插件)辅助定位。
总结
z-index 只在同一个层叠上下文内有效。遇到弹窗压不住的情况,先检查祖先元素是否创建了层叠上下文,再决定是移动 DOM 还是调整层级。理解层叠上下文,才能从根本上解决问题。