背景
在WEBVIEW的H5页面中如果有弹窗,滚动操作一般会穿透弹窗蒙层,而可以滚动弹窗下面的窗口;
分析
- 在PC端我们一般通过使用 position: fixed 来控制弹窗的绝对定位
- 弹窗组件的接口一般如下:
.dialog-mask {
position: fixed;
top: 0;
width: 100%;
height: 100vh;
z-index: 1000;
background: rgba(0, 0, 0, 0.5);
}
<section class="dialog-mask">
<div class="dialog-content"></div>
</section>
一般会有一个半透明蒙层,而内容在蒙层中绝对定位 或者 通过flex定位
3. 这个在PC端展示就没有问题,但是在手机端就会出现弹窗后背景可以滚动的问题,就是所谓的穿透
4. 那么如果我们让背景也和弹窗一样处于position:fixed的状态是否就可以了呢,答案是肯定的,但是我们有几情况如果不注意,就会带来副作用
副作用
- 一般我们很容易想到把body当做背景,我们在弹窗展示的时候,设置body的position为fixed,这时候会发现如果body内容被撑开,背景还是可以滚动,那么我们再把overflow设置为hidden;会发现并没有什么效果
- 那么我们会想到再把html当做背景,设置html的position为fixed,再把overflow设置为hidden;会发现在webview中背景固定了,但是背景页面会滚动到顶部,如果不做处理,关闭弹窗后背景页面位置变化了;另外一个副作用就是在有的浏览器这个处理也没有什么效果
最终解决
- 一般来讲,我们会把dialog和文档内容放在同一个容器,其实我们只需要设置dialog的父容器的样式即可解决这个滚动穿透的问题:
<section class="scroll-test" style="position: fixed; overflow: hidden;">
<div class="page"></div>
<section class="dialog-mask">
<div class="dialog-content">
<div class="inner-content"></div>
</div>
</section>
</section>
- 我们在弹窗时设置父元素的position和overflow,同时父容器需要设置width:100%;height:100%;这样可以保证设置fixed的时候内容不会滚动到顶部,从而解决最终问题,最终代码(VUE版本)如下:
<!--
* @Author: wahrheit
* @Date: 2020-08-17 19:19:43
* @LastEditTime: 2021-05-14 11:41:04
* @LastEditors: wahrheit
* @Description: 弹窗组件
* @FilePath: \xxx\src\components\mask.vue
* 一往无前的唯一动力就是热爱你所做的一切
-->
<style lang='less' scoped>
.dialog-mask {
position: fixed;
overflow: hidden;
top: 0;
width: 100%;
height: 100vh;
z-index: 1000;
background: rgba(0, 0, 0, 0.5);
}
</style>
<template>
<section class="dialog-mask" v-show="!close">
<slot></slot>
</section>
</template>
<script>
export default {
name: 'DialogMask',
props: {
close: {
type: Boolean,
default: true
}
},
data() {
return {
offsetHeight: 0
}
},
watch: {
close(newVal, oldVal) {
const container = this.$el.parentElement
if (newVal === false) {
container.style.position = 'fixed'
container.style.overflow = 'hidden'
} else {
container.style.position = 'absolute'
container.style.overflow = ''
}
}
},
destroyed() {
const container = this.$el.parentElement
container.style.position = 'absolute'
container.style.overflow = ''
}
}
</script>
<!--
* @Author: wahrheit
* @Date: 2021-05-13 14:52:08
* @LastEditTime: 2021-05-13 15:58:22
* @LastEditors: wahrheit
* @Description: 父组件测试
* @FilePath: \xxx\src\containers\test\scrollTest.vue
* 一往无前的唯一动力就是热爱你所做的一切
-->
<style lang='less' scoped>
section {
position: relative;
margin: 0;
width: 100%;
height: 100%;
min-height: 100vh;
font-size: 0;
overflow: auto;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
* {
box-sizing: border-box;
}
.page {
width: 100%;
height: 200vh;
background: linear-gradient(45deg,red,green);
}
.dialog-content {
width: 80%;
height: 60vh;
border-radius: .2rem;
background: #fff;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
top: 50%;
transform: translateY(-50%);
overflow: scroll;
.close-btn {
position: absolute;
width: .8rem;
height: .8rem;
border-radius: 50%;
background: blue;
color: #fff;
font-size: .23rem;
display: flex;
align-items: center;
justify-content: center;
right: .2rem;
top: .2rem;
}
.inner-content {
width: 100%;
height: 100vh;
background: yellow;
}
}
}
</style>
<template>
<section class='scroll-test'>
<div class="page" @click="show=true"></div>
<dialog-mask :close="!show">
<div class="dialog-content">
<a href="javascript:;" class="close-btn" @click="show=false">
关闭
</a>
<div class="inner-content"></div>
</div>
</dialog-mask>
</section>
</template>
<script>
import Mask from 'components/mask.vue'
export default {
name: 'ScrollTest',
data() {
return {
show: false
}
},
components: {
dialogMask: Mask
}
}
</script>
以上只处理了单层嵌套弹窗情况,如果弹窗组件被很深层次的子组件引用,需要指定相应的父组件,大家可以尝试做个兼容