微信小程序

背景

俗话说,产品有三宝:弹窗、浮层加引导,足以见弹窗在产品中的地位。对任意一个刚入门的前端同学来说,实现一个模态框基本都可以达到信手拈来的地步,但是,当模态框里边的内容滚动起来以后,就会出现各种各样让人摸不着头脑的问题,其中,最出名的想必就是滚动穿透。


什么是滚动穿透?

滚动穿透的定义:指滑动顶层弹窗,但效果上却同时滑动了底层的内容。


解决方案具体分析

改变顶层
从穿透的思路考虑,如果顶层不会穿透过去,那么问题就解决了,尝试给蒙层加catchtouchmove,但发现部分场景无效果,且顶层自己也无法滑动(真机调试)。


改变底层
既然是顶层影响了底层,要是底层不会滚动,那就没这个问题了。


如何改变底层解决该问题呢?

不成熟方案
1、底部页面最外层view设置position: fixed;页面不可滚动,但是这个时候会导致页面回到顶部。
2、滚动时监听滚动距离,弹窗时记录滚动位置,关闭弹窗后使用wx.pageScrollTo回滚到记录的位置。


成熟方案
1、使用page-meta组件,通过该组件我们可以操作Page的style样式,类似于h5里body设置overflow: hidden;控制页面不可滚动。
2、使用wx.setPageStyle设置overflow: hidden, 也可以实现给Page组件设置样式。


page-meta组件

通过该组件就可以直接操作Page组件,给它的wxss样式overflow动态设置hiddenorvisibleorauto就可以控制整个页面是否可以滚动。


禁止滑动


wx.setPageStyle方法

调用wx.setPageStyle这个api,动态设置它为hidden/auto,用于控制页面是否可滚动,主要用于页面组件内,比如封装好的弹窗组件,就不用单独写page-meta组件了。

wx.setPageStyle({
   style: {
     overflow: 'hidden' // 'auto'
   }
});

wx.setPageStyle方法详解

版本支持
wx.setPageStyle基础库最低支持版本3.3.0+,用来动态修改页面的样式。


设置

wx.setPageStyle({
  style: {
    overflow: 'hidden'
  }
});

等价于把页面的body或根节点的overflow设置成hidden,效果就是「禁用滚动」。


恢复(自动)

wx.setPageStyle({
  style: {
    overflow: 'auto'
  }
});

恢复(移除样式)

wx.setPageStyle({
  style: {
    overflow: ''
  }
});

推荐用法
禁用滚动:overflow: 'hidden'
恢复滚动:overflow: 'auto'overflow: ''


拓展

支付宝小程序虽然存在page-meta组件,但是由于内核为69版本,给page设置overflow: hidden;也无法控制底部元素不可滚动,目前已联系支付宝的底层开发人员提供API控制页面disableScroll,目前正在封装Appx,近期开放。

my.setPageScrollable({
  scrollable: true,
  success: res => {
      console.log(res);
  },
  fail: err => {
      console.log(err);
  },
  complete: res => {
      console.log(res);
  }
});

2025年6月18日API已开放,支付宝小程序测试时发现bug,安卓设置禁止滚动后,弹窗内的可滚动区域也会被禁止,IOS正常,且该问题暂时无法解决。
原因:由于系统实现层面的差异,安卓与iOS对于滚动禁止的层级控制存在区别。
安卓端采用Webview级滚动限制(全页面锁定),生效时界面及所有弹层均不可滚动;
iOS端采用组件级滚动限制(局部锁定),当弹层激活时会智能区分层级,仅限制底层页面滚动而保持弹层可滚动。


uniApp

问题

最近使用uniApp开发一款软件,父页面是可上下滑动的长列表,自定义组件弹窗遮罩出来后,滑动屏幕,父页面也跟着滚动。
在遮罩层的最外层view元素中加入@touchmove.stop.prevent="moveHandle"@catchtouchmove="moveHandle"
亲测有效,但不理想,会导致弹窗页面也无法滚动,或者处理不当就报一些其他错误,还是不太简便。


解决

问题根源是浏览器的滚动穿透,目前还没有根本性办法解决,但可以在弹窗遮罩出来时给最外层容器设置高度来解决,亲测这个办法还是比较完美。
弹出遮罩时动态设置页面最外层容器style属性的height值为100vh。 这里最好使用vh单位,100vh表示整个屏幕的高度,因为页面没有超出一屏,所以在弹窗上滑动时便不会触发底层的滚动事件,自然不会再有滚动穿透的问题。
此做法需要自定义列表滚动容器,也不是个很好的办法,表示无解


代码

<template>
	<view class="container" :style="showMsk ? 'height: 100vh' : ''">
		<!-- 滚动列表 -->
		<scroll-view scroll-y="true"></scroll-view>
		<!-- 自定义弹窗 -->
		<view v-if="showMsk" class="msk"></view>
		<!-- 自定义弹窗 -->
		<!-- <view v-if="showMsk" class="msk" @touchmove.stop.prevent="moveHandle"></view> -->
	</view>
<template>

data() {
	return {
		showMsk: false,
	};
},
methods: {
	moveHandle: {
		return false;
	}
}

更多推荐