遮罩层全屏覆盖但底层仍滚动,根本原因是未真正锁定body滚动能力;必须结合position:fixed+scrollTop保存/恢复,而非仅依赖overflow:hidden或pointer-events:none。

遮罩层能全屏盖住页面,但底下的页面依然在滚动——这不是遮罩没生效,而是你没真正锁住 body 的滚动能力。单纯用 position: fixed 做遮罩,只解决“视觉覆盖”,不解决“滚动拦截”。
为什么 overflow: hidden 在 iOS 上经常失效
iOS Safari 对 body { overflow: hidden } 的支持极差,尤其在页面已滚动过的情况下:它既不阻止滚动,也不保持当前滚动位置,甚至可能触发回弹到顶部。这不是 bug,是 WebKit 的渲染策略决定的——body 不是标准滚动容器,它的 overflow 行为被视口和 documentElement 交叉影响。
- 别把
overflow: hidden直接写死在body标签上,它会被系统或第三方库覆盖 - 移动端必须配合
position: fixed才能可靠锁定,但要手动保存并恢复scrollTop - 若项目用了
scroll-behavior: smooth,加overflow: hidden可能导致过渡动画卡顿或中断
用 position: fixed 锁定 body 的正确姿势
核心不是“让遮罩固定”,而是“让页面内容失去滚动能力”。把 body 自身设为 position: fixed,等于把它钉在视口里,自然无法再拖动。
- 显示遮罩时执行:
document.body.style.position = 'fixed'; document.body.style.top = `-${window.scrollY}px`; - 隐藏遮罩时还原:
document.body.style.position = ''; document.body.style.top = '';,再调用window.scrollTo(0, scrollY) - 必须在设置
position: fixed后立即记录window.scrollY,否则 Safari 可能已重置该值 - 避免对
html元素也设overflow: hidden,它在部分安卓 WebView 中会干扰input聚焦后的键盘适配
pointer-events: none 和滚动穿透不是一回事
给遮罩加 pointer-events: none 只能让点击/悬停事件透过去,但 touchmove 和滚动行为完全不受影响。用户手指在遮罩上滑动,只要遮罩没占满整个视口(比如有留白或缩放),底层页面仍可能响应滚动。
立即学习“前端免费学习笔记(深入)”;
- 滚动是否穿透,取决于:① 底层容器是否有
overflow-y: auto;② 遮罩是否z-index足够高且无层叠上下文污染;③ 是否存在未被覆盖的可滚动区域(如textarea) - 不要依赖
pointer-events: none来“允许滚动”,它对滚动无效;真要滚动穿透,应确保遮罩不覆盖滚动容器的 hit area - 如果遮罩内含关闭按钮,记得给按钮显式加
pointer-events: auto,否则它会被父级的none拦截
移动端 fixed 遮罩错位与抖动怎么防
iOS Safari 的 position: fixed 在地址栏收起、键盘弹出、横竖屏切换时极易偏移,尤其当页面高度超过一屏又用了 100vh。抖动则多来自滚动条消失导致的 body 宽度跳变。
- 遮罩高度别用
100vh,改用min-height: 100%; height: 100%,并确保html, body { height: 100% } - 防抖动:给
body加padding-right: calc(100vw - 100%),让滚动条始终“占位”,避免宽度突变 - 键盘弹出时,监听
focusin事件临时隐藏遮罩(display: none),失焦后再恢复,否则遮罩可能卡在页面中部 - 避免在遮罩上用
backdrop-filter: blur()+transition,Safari 渲染延迟明显,用户会感知到“卡顿后才模糊”
最易被忽略的一点:遮罩的 z-index 必须高于所有可能创建新层叠上下文的祖先元素。哪怕你设了 z-index: 9999,只要它挂在一个带 transform: translateZ(0) 的容器里,实际层级就只在那个容器内部有效——遮罩得直接挂到 body 下才算真正“全屏”。


















