ios上body加overflow:hidden失效,因safari将滚动视为viewport级行为,需配合position:fixed、记录并还原scrolltop实现可靠滚动锁定。

body加overflow: hidden为什么在 iOS 上失效
因为 iOS Safari 把 body 滚动视为 viewport 级别行为,即使设置了 overflow: hidden,手指拖拽仍会触发原生滚动(包括橡皮筋效果)。PC 端有效,但真机上基本等于没写。
常见错误现象:document.body.style.overflow = 'hidden' 后,弹窗一打开,页面还是能上下拖动;或者拖到边界时出现“回弹”,用户误以为页面卡顿。
- 不要单独依赖
overflow: hidden,尤其不能只加在body上 - 如果非要走 CSS 路线,必须配合
position: fixed+top: -scrollTop + 'px'才可能生效 - 注意:加
fixed会导致body回滚到顶部,必须提前记录document.scrollingElement.scrollTop
用 touchmove + preventDefault 的坑在哪
直接在遮罩层监听 touchmove 并调用 e.preventDefault() 是最常写的代码,但它会无差别拦截所有触摸滚动 —— 包括你弹窗内部本该可滚动的列表。
典型副作用:modal-content 里有个长选项列表,用户根本滑不动;或者 iOS 上即使加了 { passive: false },iOS 11+ 仍可能忽略该阻止。
- 必须判断
e.target是否属于弹窗内部可滚动区域,再决定是否preventDefault - 对可滚动子元素(如
.modal-scrollable)要显式放行:监听其touchmove并设e.returnValue = true(旧版)或不阻止 -
passive: false必须显式传入addEventListener,否则 iOS Safari 会静默忽略preventDefault
真正可靠的方案:fixed 定位 + 滚动位置快照
这是目前兼容性最好、无明显副作用的通用解法。核心逻辑不是“禁用滚动”,而是“把 body 临时钉住,并记住它原来在哪”。
关键步骤分三步:开弹窗前存 scrollTop → 加 position: fixed 和偏移 → 关弹窗时还原位置并清除样式。
- 存值用
document.scrollingElement.scrollTop(兼容document.documentElement或document.body) - 设置
document.body.style.top = -scrollTop + 'px',同时加position: fixed; width: 100% - 关闭时先移除
style.top和position,再立即赋值scrollingElement.scrollTop = scrollTop - 注意:赋值
scrollTop后需强制重排(如读取offsetHeight),否则部分 Android 浏览器可能延迟生效
为什么不能只靠 CSS 类名切换
很多人封装成 body.modal-open 类,靠 CSS 控制 overflow 和 position。问题在于:类名切换是异步的,样式计算和渲染有延迟,而 touch 滚动事件可能在样式生效前就已触发。
更隐蔽的问题是:某些安卓 WebView 在 class 切换后,scrollTop 会意外归零,导致关弹窗后页面跳顶。
- 必须用
style行内样式控制,确保 DOM 更新与样式应用同步 - 避免在
transition或animation过程中操作滚动锁定,动画帧会干扰滚动状态判断 - 如果使用框架(如 Vue/React),注意在
mounted/useEffect中绑定事件,且在unmounted/ 清理函数中及时解绑touchmove
scrollTop 快照时机、passive 显式声明、以及 fixed 偏移是否带单位这些地方。漏掉任一环,真机上就大概率翻车。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











