SCSS 的 $z-index-map 无法解决真实 DOM 层叠问题,因其仅在编译期生成静态值,不感知运行时 stacking context 变化;真正有效的是 CSS 自定义属性配合 JS 动态控制。

SCSS 的 $z-index-map 不能管理层叠上下文,它只生成静态值,对运行时 stacking context 完全无感。
为什么 $z-index-map 在真实项目里会失效
你写 $z-index: (modal: 1000, tooltip: 950);,再用 map-get($z-index, modal) 插入 CSS,输出的永远是 z-index: 1000 —— 这个数字编译完就固化了,不随 DOM 变化、不响应 Portal 挂载、不感知父容器是否因 transform 或 opacity 创建了新 stacking context。
常见失效场景:
- Modal 内打开 Dropdown,
z-index: 950被 Modal 自身的z-index: 1000盖住 —— 实际因为 Modal 容器触发了transform,子元素已无法“越界”提升层级 - 多个
createPortal组件都挂到body,但没做 z-index 递增逻辑,后打开的 Modal 反被先打开的压在底下 - 动画中先改
transform再提z-index,中间帧因 stacking context 切换导致按钮闪一下被遮住
真正能响应运行时的方案:CSS 自定义属性 + JS 动态控制
把层级决策权交还给浏览器和 JS,而不是让 SCSS 在编译期拍板。
关键做法:
- 在
:root中定义语义化变量:--z-modal: 1000;、--z-toast: 800;,而非 SCSS 变量 - 需要临时提层(如 hover 下拉)时,用 JS 改写:
element.style.setProperty('--z-dropdown', '1050'); - 避免在 SCSS 中做数值计算,比如
$z-modal + 10—— 这会让调试和覆盖变得更难,且毫无 runtime 意义 - Portal 组件挂载前,JS 主动读取当前最高
--z-modal值并 +1 后写入,保证顺序
别忽略 body 的 stacking context 状态
Portal 类组件常挂到 body,但 body 默认不是定位元素,也不自动构成 stacking context。这意味着它的子元素(如 Modal)的 z-index 是和页面所有其他定位元素“平级比大小”的。
容易踩的坑:
- 没检查
body是否有position: relative或z-index,结果你的--z-modal: 1000被别人一个z-index: 1001的全局 fixed bar 直接盖掉 - 多个第三方库都往
body插元素,却没人协调 z-index,最后靠“谁样式写得晚谁赢”来决胜 - 用
will-change: transform触发优化时,意外创建了新 stacking context,导致原本能透出的子元素被截断
复杂点从来不在怎么设数字,而在于 stacking context 是嵌套的、动态的、由多种属性共同触发的 —— 你没法靠一张 SCSS 表格穷举所有组合。真正要盯住的,是每个 Portal 挂载点的父容器是否“干净”,以及每次 z-index 变更是否同步触发了 context 重排。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











