标签页切换时加遮罩是为了防止用户误操作和内容错乱,必须用 position: fixed + z-index≥9999 覆盖全屏,并在数据加载全程控制显隐,同时处理 iOS 滚动穿透与焦点问题。

标签页切换时加遮罩,不是为了“看起来高级”,而是防止用户在 DOM 重绘/数据加载未完成时误点、重复提交或看到空白/错位内容。直接用 display: none 切换 tab 内容不加遮罩,多数场景下都算缺陷。
为什么不能只靠 visibility 或 opacity 切换 tab 内容
很多人把 tab 切换做成纯 CSS 显隐(比如给非激活 tab 加 visibility: hidden 或 opacity: 0),这会导致两个实际问题:
- 如果新 tab 内容依赖异步加载(如
fetch渲染表格),用户会看到空白区或旧数据残留 -
opacity: 0的元素仍占据布局流、可被聚焦、键盘可 tab 进去——这不是“遮住”,是“藏起来但还活着” - 更隐蔽的坑:
visibility: hidden不阻止 pointer-events,默认仍可点击子元素(除非显式设pointer-events: none,但那样也拦不住键盘操作)
遮罩层必须用 position: fixed + z-index ≥ 9999
标签页容器通常是相对定位的区块(position: relative),若遮罩层用 position: absolute,它只会盖住该容器内部,滚动后立刻失效;而 tab 切换常伴随页面滚动或高度变化,必须确保遮罩始终覆盖整个视口。
- 正确写法:
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; - 别用
width: 100%; height: 100%:body 高度未撑满时,100% 会塌陷 - 别省略
top: 0; left: 0:fixed 元素不设偏移会按文档流位置渲染,可能偏移出视口 - z-index 至少 9999:避免被 Ant Design、Element Plus 等 UI 库的弹窗层级压住
JS 控制遮罩显隐要绑定到 tab 切换逻辑里,而非 DOMContentLoaded
遮罩的显示时机必须和 tab 数据加载强绑定。常见错误是监听了 click 就立即 show,但此时数据还没回来;或者只在 success 回调里 hide,失败时遮罩永远卡住。
立即学习“前端免费学习笔记(深入)”;
- 显示遮罩:在点击 tab 按钮后、发起请求前,立即执行
overlayEl.style.display = 'block'(或添加类名) - 隐藏遮罩:必须在所有路径下执行,包括
fetch().then().catch().finally()的finally块,或 jQuery 的complete钩子 - 防重复触发:加一个
isSwitching标志位,避免用户连点导致多次 show/hide 冲突 - 不要用
setTimeout模拟延迟 hide:真实加载时间不可控,硬延迟反而暴露白屏
移动端 Safari 的滚动穿透和键盘焦点必须手动处理
标签页切换时,iOS Safari 不会自动冻结 body 滚动,用户滑动遮罩区域,背景页跟着滚;同时,焦点仍可进入已隐藏 tab 的 input,按 Tab 键会跳进不该出现的地方。
- 打开遮罩时:
document.body.style.cssText = 'position: fixed; width: 100%; top: -' + window.scrollY + 'px;',并缓存scrollY - 关闭遮罩时:恢复
body.style.cssText = '',再执行window.scrollTo(0, savedScrollY) - 焦点管理:遮罩显示后,用
document.activeElement.blur()主动移除焦点,并在遮罩隐藏后,把焦点 focus 到新 tab 的第一个可交互元素(如tabPanel.querySelector('button, input, [tabindex]')) - 别依赖
overflow: hidden:iOS 下对 body 生效不稳定,且可能引发回弹错位
最易被忽略的是:遮罩层本身不能有 transform 或 filter(如 blur()),否则会创建新的层叠上下文,导致其 z-index 在局部生效,压不住外部弹窗或 fixed 导航栏——真正起作用的,永远是那个干净、固定、高 z-index 的空 div。



















