z-index 失效时优先检查祖先元素的 transform 或 will-change;遮罩层应挂载到 body 下,全屏用 position: fixed,局部用 position: absolute(父级需 relative);点击关闭需 e.target === this 判断,移动端需 pointer-events: none + touch-action: none 防穿透。

z-index 失效时优先检查祖先元素的 transform 或 will-change
遮罩层明明设了 z-index: 9999,却还是被按钮、导航栏或轮播图盖住——这不是 z-index 写小了,而是某个父级(甚至 body 上级)触发了新的层叠上下文。常见元凶包括:transform: translateZ(0)、will-change: transform、opacity: 0.99、filter: blur(1px)。这些属性会让该元素成为“层叠容器”,其内部所有子元素的 z-index 都只在它内部比大小。
临时验证方法:给疑似父容器加 transform: none !important,看遮罩是否立刻浮现;根治方式是把遮罩 DOM 直接挂到 document.body 下,绕开深层嵌套结构。
特别注意:backdrop-filter 在 Safari 中也会因祖先 overflow: hidden 或 transform 被静默禁用,不是 bug,是规范行为。
遮罩用 position: fixed 还是 position: absolute?看场景
全屏模态弹窗、加载中提示、权限拦截页——必须用 position: fixed,配合 width: 100vw 和 height: 100vh。用 100% 容易在有滚动条时漏掉几像素,尤其在 macOS Safari 下明显。
局部遮罩(比如只盖住一个卡片或表格行)才用 position: absolute,但前提是其**直接父容器已设 position: relative**。否则遮罩会相对于 body 或最近的定位祖先定位,飘到页面左上角。
别混用:fixed 遮罩里嵌 absolute 弹窗是安全的;反过来,absolute 遮罩里放 fixed 弹窗会导致弹窗脱离遮罩控制范围,滚动时错位。
点击遮罩关闭弹窗,e.target === this 判断不能省
只监听遮罩的 click 事件,点弹窗里的按钮、输入框、链接也会触发关闭——因为事件冒泡没被拦截。正确写法是严格限定目标为遮罩自身:
overlay.addEventListener('click', function(e) {
if (e.target === this) {
closeDialog();
}
});
这个判断能过滤掉所有子元素的点击。如果弹窗内容区域用了 stopPropagation(),那反而会破坏这个逻辑,所以不建议在弹窗内部随意阻止冒泡。
移动端还要额外防点击穿透:遮罩移除瞬间,手指抬起动作可能误触底层按钮。稳妥做法是在 closeDialog() 里先设 overlay.style.pointer-events = 'none',再用 setTimeout(() => { overlay.remove(); }, 0) 延后移除 DOM。
移动端触摸卡顿、滑动穿透?补 touch-action: none
iOS Safari 和部分安卓 WebView 中,遮罩层默认允许缩放、双指拖拽,导致手指划过时页面轻微偏移,甚至触发橡皮筋效果。加一行 CSS 就能解决:
#overlay {
touch-action: none;
}
但注意:如果遮罩内部有可滚动区域(如长列表、富文本),不要给整个遮罩设这个属性,而应单独给滚动容器设 touch-action: auto 或 pan-y,否则内部滚动会失效。
另一个隐藏坑:pointer-events: none 在 iOS 上对 touchstart 不完全可靠,所以遮罩关闭过渡阶段仍建议保留 touch-action: none 配合 pointer-events: none 双保险。
遮罩看似简单,真正难的是边界场景:Safari 的 backdrop-filter 限制、iOS 的点击穿透时序、祖先 transform 创建的隐式层叠上下文——这些地方一漏,就是线上用户看到的“遮不住”“关不掉”“点错了”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











