公司有个项目用的vux,但是存在滑动穿透,就是整个页面有滚动条,pop弹出框也有滚动条,但是在pop滑动时,会滑动整个页面的滚动条,而我们希望当出现弹出框时,操作不要涉及到后面的页面。
滑动穿透本质上是因为弹出框和后面的页面是兄弟的层级,所以当用户操作时,弹出框和页面都出现了滑动。
第一种办法是监听弹出框显示事件,当唤起pop时,body变为overflow:hidden。pop消失时,overflow为auto。但这种办法存在兼容的问题,有的安卓不起作用。
还有一种办法,当pop弹出时,body的position设为fixed,但这时top是为0的,所以页面的位置发生了变化,我们来解决一下这个办法,代码如下
let top = 0
let showPop = false
watchPop (showPop) {
if (showPop) {
// document.getElementsByTagName('body')[0].style.overflow = 'hidden'
let body = document.getElementsByTagName('body')[0]
let app = document.getElementById('app')
let scrollTop = -app.getBoundingClientRect().top
top = scrollTop > app.offsetHeight ? (app.offsetHeight - window.innerHeight) : scrollTop //
body.style.top = (-top) + 'px'
body.style.position = 'fixed'
} else {
// document.getElementsByTagName('body')[0].style.overflow = 'auto'
let body = document.getElementsByTagName('body')[0]
body.style.position = null
console.log(top)
window.scrollTo(0, top)
}
}
以上方法需要获取pop弹出的时机,在vue里可以watch pop对应show的状态。但此时如果页面很多,或者用到pop的地方很多,watch pop就会是一个重复编写的过程,有多少个pop,watch多少次。解决这个问题最好的办法是自己封装一个组件,把pop放到自己的组件,页面引进自己的组件,在自己的组件处理。但如果已经有很多地方用了,也没关系,下面就是解决方案
let top = 0
const watchPop = (showPop) => {
if (showPop) {
// document.getElementsByTagName('body')[0].style.overflow = 'hidden'
let body = document.getElementsByTagName('body')[0]
let app = document.getElementById('app')
let scrollTop = -app.getBoundingClientRect().top
console.log(scrollTop)
top = scrollTop > app.offsetHeight ? (app.offsetHeight - window.innerHeight) : scrollTop
console.log(top)
body.style.top = (-top) + 'px';
body.style.position = 'fixed';
} else {
// document.getElementsByTagName('body')[0].style.overflow = 'auto'
let body = document.getElementsByTagName('body')[0];
body.style.position = null
console.log(top)
window.scrollTo(0, top)
}
}
let showPop = false
setTimeout(() => {
let targets = document.querySelectorAll('.vux-popup-mask')
console.log(targets.length)
targets.forEach(target => {
var options = {
attributes: true // 只观察class属性
}
var mb = new MutationObserver(function (mutationRecords) {
mutationRecords.forEach(record => {
if (String(record.target.getAttribute('class')).indexOf('vux-popup-show') === -1) {
// 不包含.vux-popup-show,pop没有打开
if(showPop) { // pop标记处于打开
showPop = false // 将pop标记为关闭
watchPop(showPop)
}
} else {
if(showPop === false) {
showPop = true
watchPop(showPop)
}
}
})
})
mb.observe(target, options)
})
}, 2500)
如果把以上代码放到mian.js,需要settimeout,因为main.js里无法直接获得对应路由的mounted回调。但仍然有风险,如果首页接口请求时间过长,大于2500毫秒,还是解决不了。所以把以上代码抽成mixin,在所需要的组件路由中引入mixin,就解决问题了
watchPopMixin () {
return {
methods: {
watchPop(showPop) {
if (showPop) {
// document.getElementsByTagName('body')[0].style.overflow = 'hidden'
let body = document.getElementsByTagName('body')[0]
let app = document.getElementById('app')
let scrollTop = -app.getBoundingClientRect().top
top = scrollTop > app.offsetHeight ? (app.offsetHeight - window.innerHeight) : scrollTop
body.style.top = (-top) + 'px';
body.style.position = 'fixed';
} else {
// document.getElementsByTagName('body')[0].style.overflow = 'auto'
let body = document.getElementsByTagName('body')[0];
body.style.position = null
window.scrollTo(0, top)
}
},
initObserver () {
let targets = document.querySelectorAll('.vux-popup-mask')
let self = this
targets.forEach(target => {
let options = {
attributes: true // 只观察class属性
}
let mb = new MutationObserver((mutationRecords) => {
mutationRecords.forEach(record => {
if (String(record.target.getAttribute('class')).indexOf('vux-popup-show') === -1) {
// 不包含.vux-popup-show,pop没有打开
if(showPop) { // pop标记处于打开
showPop = false // 将pop标记为关闭
self.watchPop(showPop)
}
} else {
if(showPop === false) {
showPop = true
self.watchPop(showPop)
}
}
})
})
mb.observe(target, options)
})
}
},
mounted() {
setTimeout(() => {this.initObserver()}, 2500)
}
}
}
我这里将以上代码放到uitls文件里了,主要是不想再创建一个文件,所以引用方式如下
mixins: [utils.watchPopMixin()],