150 lines
3.1 KiB
Vue
150 lines
3.1 KiB
Vue
<template>
|
||
<div
|
||
v-show="maskLayerShow"
|
||
class="mask-layer"
|
||
@mousedown.stop
|
||
@mouseup.stop
|
||
@pointerdown.stop
|
||
@pointerup.stop
|
||
@touchstart.stop
|
||
@touchend.stop
|
||
@touchmove.prevent
|
||
@wheel.prevent
|
||
>
|
||
<div class="mask-layer__content" @touchmove.stop v-touch-tap.self.stop="emitClose">
|
||
<slot></slot>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onUnmounted, watch } from 'vue'
|
||
|
||
const props = defineProps({
|
||
maskLayerShow: {
|
||
type: Boolean,
|
||
required: true,
|
||
},
|
||
lockScroll: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
})
|
||
const emit = defineEmits(['close'])
|
||
|
||
const emitClose = () => {
|
||
emit('close')
|
||
}
|
||
|
||
let lockCount = 0
|
||
let lockedScrollY = 0
|
||
let previousBodyStyle = {}
|
||
let previousHtmlStyle = {}
|
||
|
||
const getStyleSnapshot = (el) => ({
|
||
overflow: el.style.overflow,
|
||
position: el.style.position,
|
||
top: el.style.top,
|
||
left: el.style.left,
|
||
right: el.style.right,
|
||
width: el.style.width,
|
||
touchAction: el.style.touchAction,
|
||
overscrollBehavior: el.style.overscrollBehavior,
|
||
})
|
||
|
||
const restoreStyleSnapshot = (el, style) => {
|
||
Object.keys(style).forEach((key) => {
|
||
el.style[key] = style[key]
|
||
})
|
||
}
|
||
|
||
// 弹窗展示时锁住背景页滚动,iOS 需要固定 body 并在关闭后恢复滚动位置。
|
||
const lockPageScroll = () => {
|
||
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
||
|
||
if (lockCount === 0) {
|
||
const body = document.body
|
||
const html = document.documentElement
|
||
|
||
lockedScrollY = window.scrollY || html.scrollTop || body.scrollTop || 0
|
||
previousBodyStyle = getStyleSnapshot(body)
|
||
previousHtmlStyle = getStyleSnapshot(html)
|
||
|
||
html.style.overflow = 'hidden'
|
||
html.style.touchAction = 'none'
|
||
html.style.overscrollBehavior = 'none'
|
||
|
||
body.style.overflow = 'hidden'
|
||
body.style.position = 'fixed'
|
||
body.style.top = `-${lockedScrollY}px`
|
||
body.style.left = '0'
|
||
body.style.right = '0'
|
||
body.style.width = '100%'
|
||
body.style.touchAction = 'none'
|
||
body.style.overscrollBehavior = 'none'
|
||
}
|
||
|
||
lockCount += 1
|
||
}
|
||
|
||
const unlockPageScroll = () => {
|
||
if (typeof window === 'undefined' || typeof document === 'undefined') return
|
||
if (lockCount <= 0) return
|
||
|
||
lockCount -= 1
|
||
|
||
if (lockCount === 0) {
|
||
restoreStyleSnapshot(document.body, previousBodyStyle)
|
||
restoreStyleSnapshot(document.documentElement, previousHtmlStyle)
|
||
window.scrollTo(0, lockedScrollY)
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => props.maskLayerShow,
|
||
(newVal) => {
|
||
if (!props.lockScroll) return
|
||
|
||
if (newVal) {
|
||
lockPageScroll()
|
||
} else {
|
||
unlockPageScroll()
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
onUnmounted(() => {
|
||
if (props.lockScroll && props.maskLayerShow) {
|
||
unlockPageScroll()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
* {
|
||
color: black;
|
||
}
|
||
|
||
.mask-layer {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background-color: #00000080;
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10000 !important;
|
||
overflow: hidden;
|
||
touch-action: none;
|
||
overscroll-behavior: none;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.mask-layer__content {
|
||
position: absolute;
|
||
inset: 0;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
overscroll-behavior: contain;
|
||
}
|
||
</style>
|