仅靠 body { overflow: hidden } 无法真正阻止 iOS 和部分安卓 WebView 的滚动穿透,必须配合 position: fixed + 滚动位置保存/恢复才能可靠生效。因为 iOS WebKit 对 overflow: hidden 不拦截 touch 默认滚动行为,视觉无滚动条但手指滑动仍触发底层惯性滚动;安卓旧版 WebView(如 UC、QQ)也有类似表现;正确做法是用 position: fixed 脱离文档流并记录 window.pageYOffset 后设 top 负值锚定位置,关闭时还原并 scrollTo 恢复,同时 modal 内部可滚动区域需显式放行 touchmove。

仅靠 body { overflow: hidden } 无法真正阻止 iOS 和部分安卓 WebView 的滚动穿透,必须配合 position: fixed + 滚动位置保存/恢复才能可靠生效。
为什么 overflow: hidden 在移动端基本失效
iOS WebKit 对 body 的 overflow: hidden 不拦截 touch 默认滚动行为,视觉上没滚动条,但手指一滑,底层页面照样惯性滚动——这不是 CSS 写错了,是浏览器机制决定的。安卓某些 WebView(如旧版 UC、QQ 浏览器内核)也有类似表现。
常见错误写法:document.body.style.overflow = 'hidden',加完发现真机上依然能拖动背景页。
用 position: fixed 锁定 body 的正确姿势
核心不是“禁滚动”,而是让 body 物理上失去滚动能力:固定定位后它脱离文档流,自然无法被拖拽滚动。
立即学习“前端免费学习笔记(深入)”;
- 打开弹窗前,先记录当前滚动位置:
const scrollY = window.pageYOffset || document.documentElement.scrollTop - 设置
document.body.style.position = 'fixed',再设document.body.style.top = `-${scrollY}px`,把页面“拉回”原位,避免跳顶 - 关闭弹窗时,先还原
document.body.style.position = ''和document.body.style.top = '',再调用window.scrollTo(0, scrollY) - 不要只改
scrollTop,iOS Safari 中document.body.scrollTop常为 0,必须优先读window.pageYOffset
modal 内部需要滚动时怎么兼顾
如果弹窗内容超长(比如长表单、选项列表),又不能让底层跟着动,就不能全局阻止 touchmove。
- 给可滚动区域(如
.modal-content)明确设max-height: 80vh; overflow-y: auto; -webkit-overflow-scrolling: touch - 在 modal 根元素上监听
touchmove,仅当触摸点不在可滚动子元素内才e.preventDefault():if (!e.target.closest('.modal-content')) e.preventDefault() - 避免对
document或body直接绑touchmove,否则会干掉textarea、input等原生可滚动控件
容易被忽略的兼容细节
这套方案在 iOS 16.4+ 上可搭配 overscroll-behavior: contain 降级使用,但注意该属性必须设在实际可滚动的内容区(如 .modal-content),而不是 .modal 容器本身——后者不可滚动,加了也无效。
另外,若项目用了 html { height: 100% } 或 body { min-height: 100vh },position: fixed 后可能因高度塌陷导致遮罩层漏底,建议统一用 height: 100vh 控制弹窗层级容器高度。


















