结论:仅设 body { overflow: hidden } 在移动端基本无效,因根滚动容器是 html 且 ios 有弹性滚动,必须同时设置 html/body 的 css、阻止 touchmove 并保存还原滚动位置。

直接说结论:仅设 body { overflow: hidden } 在移动端基本无效,iOS Safari 会无视它,安卓部分 WebView 也照样滚动。真要锁住,必须同时控制 html、body 的 CSS + DOM 行为 + touch 事件响应。
为什么 body { overflow: hidden } 在手机上不生效
根本原因有两个:
- 页面根滚动容器是
html元素,不是body;Chrome/Firefox/iOS Safari 都把 viewport 滚动交由html管理,只动body样式等于没动根节点 - iOS Safari 有弹性滚动(rubber-band),即使
html和body都设了overflow: hidden,手指拖拽仍会触发回弹并最终滚动 - 某些框架(如 React Router)或第三方脚本会主动调用
window.scrollTo(),CSS 锁不住 JS 主动滚动
必须同步做的三件事(缺一不可)
手动实现滚动锁定时,以下三点必须一起处理:
- 给
html, body同时加overflow: hidden; height: 100%—— 不加height: 100%,html可能因内容撑高而产生隐式滚动空间 - 在
touchstart阶段就监听document的touchmove事件,并调用event.preventDefault(),且必须传{ passive: false };等到touchmove再绑就晚了 - 保存并还原滚动位置:
document.documentElement.scrollTop和document.body.scrollTop在不同浏览器中可能不同,关闭遮罩时需分别还原
更稳妥的替代方案:用 position: fixed 冻结 body
iOS 下最可靠的方式不是靠 overflow,而是把 body “钉住”:
- 弹出遮罩时:
document.body.style.position = 'fixed'; document.body.style.top = '-' + window.pageYOffset + 'px'; - 关闭遮罩时:先还原
scrollTop,再清除position和top,否则页面会跳到顶部 - 注意:若
body原有margin或padding,会导致视觉偏移,建议初始化时重置html, body { margin: 0; padding: 0; }
遮罩层内容自己要能滚,但别漏掉这个细节
很多人设完锁定后发现遮罩里的长内容也不能滚动了——这不是 bug,是预期行为。因为 touchmove 的 preventDefault() 是全局拦截的。
- 解决方案:只对非遮罩区域的
touchmove调用preventDefault(),或者给遮罩层加touch-action: auto - 更简单做法:确保遮罩层自身有固定高度 +
overflow-y: auto,并在其内部元素上设置overscroll-behavior: contain(兼容性需查 MDN) - 关键点:
body-scroll-lock这类库之所以好用,是因为它自动做了目标元素可聚焦判断和overscroll-behavior降级,手动实现时容易忽略这点
真正麻烦的从来不是“怎么写一行 overflow”,而是“怎么让所有设备都按你想的停住”。iOS 的 rubber-band、安卓 WebView 的被动事件策略、JS 主动滚动、甚至 transform 创建的新滚动上下文,都会让单一 CSS 失效。动手前先确认你锁的是谁的滚动,以及谁在偷偷把它拉回来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











