必须用css自定义属性(如--z-modal)管理z-index,因其运行时可读、可继承、可动态更新;sass变量(如$z-modal)仅编译期有效,js无法读取,无法支持主题切换、hover提层等动态场景。

必须用 CSS 自定义属性(--z-modal 这类)管理 z-index,Sass 变量仅适用于编译期静态场景,无法被 JS 读取或运行时调整——这是线上弹窗遮挡、Tooltip 消失等事故的根源。
为什么 --z-modal 比 $z-modal 更适合现代项目
CSS 自定义属性是运行时可访问、可继承、可动态更新的;Sass 变量在编译后就消失了,JS 拿不到,主题切换、暗色模式、hover 提层等都做不到。
-
--z-modal: 1000可以被getComputedStyle(el).getPropertyValue('--z-modal')读取,也可用el.style.setProperty('--z-modal', '1050')覆盖 -
$z-modal编译后只剩z-index: 1000,一旦需要动态提升层级(比如 Modal 打开时让 Dropdown 上浮),就得硬写内联样式或 JS 拼字符串,极易出错 - 多个组件库共存时(如 Headless UI + Ant Design),CSS 变量可通过
:root统一覆盖基线;Sass 变量作用域隔离,跨包无法对齐
怎样定义不冲突、可扩展的 --z-* 变量
核心是“留空档、分职责、禁魔法值”——不是按顺序填数字,而是按功能预留增长空间。
- 基础层:
--z-background: -10(用于 canvas 底层、SVG mask) - 内容层:
--z-card: 0、--z-table-row: 1(默认文档流元素,不设定位也无需 z-index) - 浮层层:
--z-tooltip: 100、--z-dropdown: 200(中间空出 99,方便插--z-combo-box: 150) - 强弹窗层:
--z-modal-overlay: 1000、--z-modal-content: 1010(overlay 和 content 必须差值明确,避免遮罩透出) - 绝对禁止:
--z-max、--z-top、--z-9999——语义缺失,且与第三方库默认值(如 Bootstrap 的z-index: 1070)直接冲突
常见失效原因:变量写了但没生效
不是语法错,而是链路断在某一处。最典型三种:
- 路径错误:
:root中定义了--z-modal,但组件 CSS 文件没加@import 'vars.css'或未通过link rel="stylesheet"加载 - 拼写不一致:定义的是
--z-modal-overlay,使用时写成var(--z-modal)或var(--z_modal_overlay)(中划线 vs 下划线) - 作用域泄露:在某个组件内部
.dropdown { --z-dropdown: 300 },结果子组件读到的是全局--z-dropdown: 200,因为自定义属性默认不继承,需显式inherit或用:host控制
Portal/Teleport 场景下 z-index 为何仍被压住
把 Modal 挂到 下不代表自动“置顶”—— 默认没有 position,不构成 stacking context,它的子元素仍和页面其他 position: fixed 元素同级竞争。
- 错误做法:直接
document.body.appendChild(modalEl),依赖 DOM 顺序,但若页面有z-index: 2147483647的广告浮层,Modal 照样被盖 - 正确做法:创建专用容器
<div id="overlay-root" style="position: relative; z-index: 2000"></div>,所有 Portal 都挂载到这里 - 更稳妥方案:在
#overlay-root上设isolation: isolate,强制它成为独立 stacking context,彻底隔绝外部干扰
真正麻烦的从来不是写多少个 z-index,而是当 --z-dropdown 被临时提至 1005 时,是否所有相关组件(Tooltip、Select、Datepicker)都同步更新了计算逻辑,以及有没有人悄悄在某个 div 上写了 z-index: 9999 ——那个数字不会报错,但会悄悄埋雷。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











