移动端弹窗遮罩层挡不住底部滚动是因 ios safari 对 body { overflow: hidden } 支持差,存在滚动穿透;需用 position: fixed + inset: 0 直接挂载到 body、设 touch-action: none,并配合 js 拦截 touchmove 事件及 body 定位微调。

移动端弹窗遮罩层为什么挡不住底部滚动
因为 iOS Safari 对 body { overflow: hidden } 的支持极差,尤其在键盘弹起、地址栏收放后,遮罩层下页面仍能惯性滚动——这不是你 CSS 写错了,是 WebKit 的滚动穿透(Scroll Penetration)机制在作祟。单纯靠 position: fixed + z-index 只能盖住视觉,拦不住 touchmove 事件往下传。
遮罩层必须用 position: fixed + inset: 0,且挂载到 document.body 最末尾
常见错误是把遮罩层写在某个组件内部,或嵌套在有 transform / will-change 的父容器里,这会创建新的层叠上下文,导致 z-index 失效,遮罩被内容层“穿透”。
- 遮罩 DOM 必须直接 append 到
document.body,结构形如:<div class="overlay"></div> <div class="modal"></div> - CSS 推荐用
inset: 0(等价于top: 0; right: 0; bottom: 0; left: 0),比width: 100vw; height: 100vh更稳,避免滚动条宽度导致的右侧露白 - 务必设
touch-action: none,否则 iOS 上滑动遮罩区域仍可能触发底层滚动
锁滚动不能只靠 overflow: hidden,得组合三招
单靠样式禁滚动在移动端基本失效,必须配合 JS 拦截事件 + 布局微调。
- 弹出时:给
document.body添加touchmove监听器,e.preventDefault(),注册时传{ passive: false } - 同时记录
window.pageYOffset,并设置body.style.position = 'fixed'; body.style.top = `-${scrollTop}px`,防止关闭后跳顶 - 关闭时:移除
touchmove监听器,恢复body.style.position和window.scrollTo(0, scrollTop) - 别忘了给遮罩层加
pointer-events: auto(默认就是,但某些重置 CSS 会干掉它)
底部弹窗和全屏遮罩共存时,焦点与滚动冲突怎么解
当底部弹出面板(比如筛选菜单)和全屏遮罩同时存在,iOS 容易出现键盘弹起后遮罩错位、点击区域失灵、或滑动时误触发页面滚动。
- 底部面板自身要用
transform: translateY()动画,而非bottom,避免重排;同时加touch-action: manipulation确保点击响应无延迟 - 若底部面板在遮罩层之上,它的
z-index必须高于遮罩(如遮罩z-index: 1000,面板设z-index: 1002),且两者不能是父子关系 - 键盘弹起时,iOS 会重算视口高度,此时需监听
focusin事件,手动调整底部面板的bottom值(例如减去键盘高度) - 最关键一点:遮罩层和底部面板的
touchmove阻止逻辑不能互相覆盖——建议统一用一个锁滚动状态管理器,避免重复绑定/漏解绑
真正难的不是写出来,而是所有机型、所有系统版本、所有键盘状态下的行为一致性。iOS 的 rubber band 效果、Android 的 overscroll glow、以及 hybrid 容器里的 WebView 行为差异,会让同一套代码在不同环境表现不一。上线前务必真机连 Safari Web Inspector 调试 touch 事件流,别信模拟器。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











