z-index 应抽成语义化 css 自定义属性并由 block 级类名统一绑定,modal--visible 必须作用于根节点,modal__overlay 必须用 fixed + inset 且避免 transform,多个弹窗需用唯一 block 名隔离。

z-index 不能写在 modal__overlay 这类元素类里
直接给 modal__overlay 写 z-index: 9999 是最常见也最危险的做法。它会让所有用了这个类的遮罩都强行浮在顶层,一旦页面出现多个弹窗(比如 modal 叠着 confirm 再叠着 toast),层级就彻底失控——后加载的不一定盖住前一个,反而可能被压在底下。
真正该做的,是把 z-index 抽成语义化 CSS 自定义属性,由 block 级类名统一绑定:
-
:root中定义:--z-modal: 1000、--z-toast: 900、--z-dropdown: 800 -
.modal上声明:z-index: var(--z-modal) -
.toast上声明:z-index: var(--z-toast) - 所有子元素(如
modal__overlay、modal__content)不再设 z-index,靠父级层叠上下文自然继承
modal--visible 必须加在根节点,不是子元素上
状态类 modal--visible 如果只加在 modal__overlay 或 modal__content 上,会导致遮罩显示了但内容没动,或者内容位移了但遮罩还黑着——体验断裂,JS 状态也难同步。
正确做法是让整个组件的状态由 block 根节点承载:
- HTML 结构必须是:
<div class="modal modal--visible"></div> - CSS 用选择器联动:
.modal--visible .modal__overlay控制遮罩显隐,.modal--visible .modal__content控制内容动画 - 避免把
modal__overlay--visible当作独立开关——它只是 modal 状态的视觉延伸,不是自治单元
遮罩层必须用 fixed + inset,别碰 transform
只要给元素加了 transform(哪怕只是 transform: translateY(0)),它就自动创建新的层叠上下文,导致父级设置的 z-index 对其内部子元素失效。而 modal__overlay 如果被套在带 transform 的容器里,就会“看不见” .modal 上设的 z-index。
解决方案很明确:
-
modal__overlay必须用position: fixed; inset: 0;,不依赖任何父级定位上下文 -
modal__content可以用transform做入场动画,但它的z-index必须提前设在.modal根节点上,而不是自己身上 - 禁止在同一个元素上同时写
transform和指望靠它参与全局 z-index 排序
多个弹窗共存时,修饰符不能复用 class 名
如果所有弹窗都用 modal--visible,JS 切换时容易误操作——比如关闭一个弹窗,结果所有 modal--visible 都被删掉。更麻烦的是,CSS 里写 .modal--visible .modal__overlay 会匹配到所有实例,无法精准控制单个。
应对方式不是改命名,而是靠结构隔离:
- 每个弹窗实例应有唯一 block 名,比如
user-modal、confirm-dialog、image-preview - 它们共享同一套 CSS 规则(通过
@apply或 Sass@extend),但 class 名不重叠 - JS 控制时操作具体实例:
document.querySelector('.user-modal').classList.toggle('user-modal--visible')
真正难的不是起名,是让 HTML 节点、CSS 层级、JS 状态三者对齐——稍有错位,遮罩就盖不住内容,或内容飘在遮罩上面。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











