最稳方案是弹窗打开时用height: 100vh; overflow: hidden动态锁定页面高度,从布局层面切断滚动条件;关闭时务必清空height样式,避免安卓WebView残留滚动条,该法兼容H5、App及小程序各端。
uni-popup遮罩层下页面还在滚动?这是滚动穿透
不是你的代码写错了,是移动端 webview 和小程序对 touchmove 事件的处理机制导致的——弹窗内容滑到边界后,事件“漏”到底层页面,触发了背景滚动。ios 微信、部分安卓 webview 尤其明显。直接加 catchtouchmove 或只在遮罩上写 @touchmove.stop.prevent 往往无效,因为事件源头不在遮罩,而在弹窗内部可滚动区域(比如 scroll-view)或页面根容器。
最稳的方案:弹窗打开时动态锁住页面高度
绕过事件拦截的不可靠性,直接从布局层面切断滚动条件。原理简单:让页面内容高度刚好等于视口,没有溢出,自然无法滚动。
- 给最外层
<view class="container"></view>绑定动态 style::style="popupVisible ? 'height: 100vh; overflow: hidden;' : ''" - 必须用
100vh,不能用100%或固定 px 值,否则在 iOS Safari 等环境会因地址栏缩放导致计算偏差 - 关闭弹窗时务必同步恢复 height 为空(不是
auto),否则某些安卓 WebView 会残留滚动条或布局错位 - 如果页面本身用了
scroll-view作为滚动容器,这个方法依然有效,因为它锁的是整个页面容器,不依赖子组件行为
为什么 @touchmove.stop.prevent 在遮罩上不管用
遮罩层只是视觉蒙版,用户真正滑动的是弹窗里的内容区(比如长文本、列表)。一旦内容区到达滚动边界,系统会把剩余的 touchmove 传递给父级滚动容器——而这个容器往往是页面根节点,不是遮罩。
- 只在遮罩层加
@touchmove.stop.prevent,等于在错误的位置设路障 - 若在弹窗根节点加,可能连弹窗自身滚动也被禁掉(比如你本想允许弹窗内上下滑动)
- H5 端部分 iOS WebView 对
.stop修饰符支持不完整,event.stopPropagation()可能被忽略 - 小程序里
catchtouchmove的行为和 H5 不一致,真机测试常有差异
App 端和小程序端要额外注意的坑
uni-app 的多端差异在这里集中爆发,同一套写法在 H5 行得通,在 App 或小程序里可能失效。
- iOS App 端:如果页面用了
scroll-view,遮罩层必须挂载到page根节点之外(例如用uni.createSelectorQuery()动态 append 到 body),否则会被scroll-view的overflow: hidden截断 - 微信小程序:不能依赖
body { overflow: hidden },因为小程序没有真实 body;必须操作页面最外层自定义容器 - Android 微信内置浏览器:对
transform: translateZ(0)敏感,遮罩层建议加上该样式启用硬件加速,避免闪烁或错位 - 所有端都别用
pointer-events: none控制遮罩交互,App 端部分版本完全不识别
关键点其实就一个:滚动穿透不是事件没拦住,而是滚动容器没关严。锁高度是目前兼容性最强、副作用最小的解法,但必须配合正确的单位和时机——100vh + 打开时设 + 关闭时清空,少一步都会在某个平台翻车。











