移动端遮罩层上面滑动穿透问题

今天在一个移动项目中遇到了一个问题,使用display: fixed设置了一个遮罩层,当上下滑动这个遮罩层到极限后后面的内容会跟着移动,开始以为是一个很简单的问题,没想到也是需要花点时间去试验各种方法。

在小程序中也是需要类似的解决方法。

阻止滚动条wheel事件

1
2
3
4
handleWheel (e) {
e.stopPropagation()
e.preventDefault()
}
1
<div class="wrap" @wheel="handleWheel"></div>

该方法在浏览器上是没问题的不会继续滚动,但是再移动端上根本不起作用,因为移动端使用的是touchstart touchmove touchend,而且touch事件还有另外一个问题,因为click事件在这三个事件之后会等待300ms来判断是否会做出双击屏幕之后才会触发。

设置为overflow: hidden

弹出遮罩层之后不想让后面的内容滚动,可以尝试在弹出的时候设置滚动元素overflow: hidden禁止其滚动,同时给html,body均添加一个overflow:hidden的属性,取消的时候恢复滚动,但是还是有问题,禁用之后滚动位置会滚动到顶部。

解决这个滚动到顶部的方法是记录当前的scrollTop,取消的时候在赋值给当前页面的scroll

弹出层的touchmove事件中调用preventDefault

1
2
3
modal.addEventListener('touchmove', function(e) {
e.preventDefault();
}, false);

这样用 js 阻止滚动后看起来效果不错了,但是也有一个缺点:

弹出层里不能有其它需要滚动的内容(如大段文字需要固定高度,显示滚动条也会被阻止)

完美解决方案

给不想让他滚动的内容使用如下css,使其不受滚动的影响同时也不需要控制其滚动事件,再控制其滚动位置达到完美解决的效果。

但是如果在vue中使用次方法,可能在路由变动的时候没有执行removclass导致body滚动效果有问题

1
2
3
4
5
.modal-open {
position:fixed;
height: 100%;
width: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const ModalHelper = (function (bodyCls) {
let scrollTop

return {
afterOpen () {
// 记录当前的滚动位置
scrollTop = document.scrollingElement.scrollTop
// 使body脱离文档流
document.body.classList.add(bodyCls)
// 把脱离文档流的body拉上去!否则页面会回到顶部!
document.body.style.top = `${-scrollTop}px`
},
beforeClose () {
// body又回到了文档流中(我胡汉三又回来啦!)
document.body.classList.remove(bodyCls)
// 滚回到老地方
document.scrollingElement.scrollTop = scrollTop
},
}
})('modal-open')

参考

移动端滚动穿透问题
Modal scrolling on mobile devices - what is the current state
解决小程序的遮罩层滚动穿透