移动端滚动穿透问题
1.问题描述
当底层页面和弹出层都有滚动的情况下,滚动弹出层的列表会导致底层页面一起滚动
2.网上查询到的方案(结合自己项目修改过了)
1. css之 overflow: hidden
{
overflow: hidden;
height: 100%;
}
将样式加到 html 上,禁用 html 和 body 的滚动条,但是这个方案有两个缺点
:
- a.由于 html 和 body的滚动条都被禁用,弹出层后页面的滚动位置会丢失,需要用 js 来还原。
- b.页面的背景还是能够有滚的动的效果
2. js 之 touchmove + preventDefault
dom.addEventListener('touchmove', function(e) {
e.preventDefault();
}, false)
这样用 js 阻止滚动后看起来效果不错了,但是也有一个缺点
:弹出层里不能有其它需要滚动的内容 3. js + css
.fixed {
position: fixed;
width: 100%;
}
openScroll() {
this.$refs.launchPush.scrollTop = this.scrollTop
document.getElementsByTagName('html')[0].classList.remove('fixed')
document.body.classList.remove('fixed')
},
// 禁用滚动
closeScroll() {
// 记录滚出去的距离,方便下次还原
this.scrollTop = this.$refs.launchPush.scrollTop //launchPush是含滚动条的元素
document.getElementsByTagName('html')[0].classList.add('fixed')
document.body.classList.add('fixed')
},
如果只是用上面的 css,滚动条的位置同样会丢失。 所以如果需要保持滚动条的位置需要用 js 保存滚动条位置关闭的时候还原滚动位置
3.实战中遇到的问题及解决方法
先大体说一下页面结构,笔者是vue项目,底层页面比屏幕高,是一个全局滚动,弹出层则由遮盖层和一个滚动列表对话框组成(兄弟元素关系)。
笔者一开始直接使用的是方案3,在开发工具上测试没有问题,但是在ios上发现仅仅只是移动一下弹出层的遮罩部分就开始出问题了,依然会滚动穿透。为了解决第一个问题,笔者采用的方法是阻止touchstart的默认行为,在vue里给遮盖层直接挂上下面的事件即可(touchStartFn函数里可以不写任何东西)
。
@touchstart.prevent='touchStartFn'
解决完这个问题后,笔者发现滚动列表部分的滚动穿透问题依然没有得到解决,在滑动中遇到的另一个问题就是当列表滚动到底部或顶部触发回弹后,滚动区就不能继续滚动了,连滚动条都不见了,非常影响用户体验。这个问题的解决方案见文章<<ios元素滚动触发回弹后会将不再滚动的bug>>
。
解决完上述两个问题后,笔者以为可以收尾了,但是事情发展不如人意。笔者在测试过程中又发现一个问题,就是当滚动列表滚动顶部或者滚动底部以后如果继续滑动,此时底层页面又会被带着一起滑动。
笔者的给出的解决方法是监听带滚动条的元素,当它的scrollTop超出极值时,则进行重置,让滑动区域始终保持在接近底部或顶部的范围内,这个可以有效解决这个bug
moveFn(e) {
var scrollTop = this.$refs.wrapList.scrollTop
var boxH = this.$refs.wrapList.clientHeight
var contentH = this.$refs.list.clientHeight
// 滚动到顶部
if(scrollTop === 0) {
this.$refs.wrapList.scrollTop = 1
}
// 滚动到底部
if((scrollTop + boxH) >= contentH) {
this.$refs.wrapList.scrollTop = contentH - boxH - 1
}
}