移动端滑动穿透,亲测有效(内容区是否滚动都行)

4,092 阅读5分钟

滑动穿透

微信图片_20220307095741.jpg

大家都知道,咱们平时写弹窗的时候经常会遇到,滑着滑着把弹窗下面的东西也滑动了。
这就是滑动穿透
造成影响是什么呢?
最显而易见的就是你做弹窗的时候,且内容区支持滚动!!!! 到了顶部或者是底部还继续继续滑动的话,然后你就会把弹窗下面的内容给划起来了。虽然不影响使用,但是如果有产品体验会的话,这个问题肯定是会被拉出来鞭尸的。


所以这边提出下我的解决方案,这个方案需要结合实际弹窗去使用,亲测有效,这个方案是我实现最好的一个了,遇到这个问题的朋友们可以尝试一下。

前言:

讲述这个问题之前,我们不妨回想下,现在通用的滑动穿透方案有什么?

1. 禁用默认事件(e.preventDefault), 缺点是内容区可以滚动的话,这个有问题

2. body加overflow:hidden,缺点不一定知道页面的滚动区(不是弹窗里面的哦)在哪,但是有意思的是IOS用这个效果不错,即便你不知道页面滚动区在哪

3. 面对内容区可以滚动的,我们结合JS监听弹窗内容滚动方式判断是否要禁用默认事件,效果还行,缺点实现比较麻烦,而且滑动很快的话,会有问题。

上述三点都是我之前查阅资料见到最多的,但都不完美。

下面我讲述的方案是结合了上方1、2点的优点实现的,效果比较完美。 关键点:

1.内容区不可滚动的话, 禁用默认事件(e.preventDefault)效果很好,所以弹窗中我们需要将不可滚动的内容和滚动区可能出现的不可滚动情况都加上e.preventDefault,下文如果出现e.preventDefault都是在处理这种情况。

2.内容区可以滚动到顶或到底的话继续滑动还是会出现滑动穿透的,我们可以通过overscroll-behavior:contain;下文有介绍,有兴趣可以去看看或者去谷歌一下。

3.body加overflow:hidden对IOS效果很好所以弹窗打开的时候可以加overflow:hidden在body上

赶时间的朋友可以结合上述三点的思路去实现下,真的有效。


正文:


大家都知道我们开发的时候一般需要适配安卓和iOS
所以就按照两种情况来处理

IOS:

针对ios我们需要做的操作非常简单就是加上overflow:hidden,当你的弹窗显示的时候body的overflow置为hidden,监听弹窗的显示即可。(IOS可以这样子搞,但是安卓好像不太行,因为滚动条不一定会在body上)

showDialog(val) {
    document.body.style.overflow = val?'hidden':'';
}

安卓:

针对安卓我们做的比较复杂,为了做到万无一失我们如图所示:

蒙层

企业微信截图_16673771302481.png
一般弹窗之外是蒙层,所以我们要做蒙层的地方加上禁用默认事件。 代码如下:

 <div class="mask" @touchmove.stop.prevent>
 <div>

弹窗滚动区之外的内容

然后针对弹窗本身的话,我们会把弹窗分成两部分处理,一部分是滚动区,另一部分是滚动区之外的内容。 首先我们是对滚动区之外的内容我们要去做默认事件的禁用。也就是图中dialog-content和title-content的地方。

企业微信截图_16673775566119.png
在dialog-content的地方加上默认事件的禁用,如遇到这两个类名就禁用。 代码如下:

// 弹窗区
<div class="dialog-content" @touchmove.stop="handleTouch">
内容
</div>
// 对应方法
handleTouch(e) {
    if (e.target.className == 'dialog-content' || e.target.className == 'title-content') e.preventDefault();
},

弹窗滚动区

最后就是最关键的弹窗滚动区了。 企业微信截图_16673783216637.png

同上我们也要对于异常情况去做禁用逻辑,如果滚动区内容较少的话,我们直接滑动滚动区一样是会穿透的,所以针对这种情况我们要做禁用逻辑。
代码如下:

// 弹窗滚动区
<div class="scroll-content" @touchmove.stop="listTouch" ref="contentScroll">
内容
</div>
// 相关方法
listTouch(e) {
    if (!(this.$refs.contentScroll.scrollHeight > this.$refs.contentScroll.clientHeight)) e.preventDefault();
},

滚动区内容少的情况下的滑动穿透就基本解决了。

但是如果滑到底部或者顶部还继续滑动的话依旧会出现滑动穿透 ,这时候可以用上一个css新属性 overscroll-behavior
链接: (overscroll-behavior - CSS(层叠样式表) | MDN (mozilla.org))

因为我们平时写这种滚动的时候会有滚动链,借鉴MDN的说明: 默认情况下,当触及页面顶部或者底部时(或者是其他可滚动区域),移动端浏览器倾向于提供一种“触底”效果,甚至进行页面刷新。你可能也发现了,当对话框中含有可滚动内容时,一旦滚动至对话框的边界,对话框下方的页面内容也开始滚动了——这被称为“滚动链”。

所以我们滚到底的时候就因为滚动链的影响滚到下面的滑动条去了。

因此我们要做滚动条所在的地方class中加上overscroll-behavior:contain;

该属性的说明:

  • contain

  • 设置这个值后,默认的滚动边界行为不变(“触底”效果或者刷新),但是临近的滚动区域不会被滚动链影响到,比如对话框后方的页面不会滚动。

代码如下:

// 弹窗滚动区
<div class="scroll-content" @touchmove.stop="listTouch" ref="contentScroll">
// 弹窗滚动区
.scroll-content {
    overscroll-behavior: contain;
    ...
}

美中不足的是这个属性的兼容性可能不是特别好(但是基本上主流的浏览器都覆盖了)

image.png

到这里的话,滑动穿透我的解决方案就到此为止了。

下面放上比较完整的伪代码,代码如下:

<template>
    <div v-if="showDialog" class="dialog">
        <!-- 蒙层 -->
        <!-- 禁用蒙层默认事件 -->
        <div class="mask" @touchmove.stop.prevent></div>
        <!-- 弹窗 -->
        <div class="dialog-content" @touchmove.stop="handleTouch">
            <!-- 弹窗标题 -->
            <div class="title-content"></div>
            <!-- 弹窗滚动区 -->
            <div class="scroll-content" @touchmove.stop="listTouch" ref="contentScroll"></div>
        </div>
    </div>
</template>
<script>
watch: {
    // 弹窗打开的时候overflow置为hidden,处理iOS的情况
    showDialog(val) {
        document.body.style.overflow = val?'hidden':'';
    }
},
methods: {
    // 滚动区之外的内容要禁用掉,通过判断类名的方法禁用(yysy比较麻烦,可以换个更好的方法)
    handleTouch(e) {
        if (e.target.className == 'dialog-content' || e.target.className == 'title-content') e.preventDefault();
    },
    // 滚动区内容比滚动区默认高度小的情况下要禁用默认事件
    listTouch(e) {
        if (!(this.$refs.contentScroll.scrollHeight > this.$refs.contentScroll.clientHeight)) e.preventDefault();
    },
}
</script>
<style lang="scss">
.dialog {
    .mask {}
    .dialog-content {
        .title-content {}
        .scroll-content {
            // 处理滚动区到顶或者到底的滚动条防止滑动穿透
            // 滚动区内容比滚动区默认高度大且到顶的情况
            overscroll-behavior: contain;
        }
    }
}
</style>