前边那个弹窗还有一些瑕疵,看得到的瑕疵。比如,使用贝塞尔曲线来模拟弹簧那种伸缩效果时,会露底。如果说是变魔术,用土话讲,那就是穿帮了。还有就是弹窗的动画的时间没有定义的地方。这次都加上了。先看看效果。
Code
popupLayout.vue
<template>
<view v-if="controller.open" class="popup-layer" catchtouchmove="true">
<view v-if="direction == 'top'" class="top-bg-color" :class="{ 'top-bg-show': controller.bg, 'top-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'bottom'" class="bottom-bg-color" :class="{ 'bottom-bg-show': controller.bg, 'bottom-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'left'" class="left-bg-color" :class="{ 'left-bg-show': controller.bg, 'left-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'right'" class="right-bg-color" :class="{ 'right-bg-show': controller.bg, 'right-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view class="popup-content" :style="_location">
<slot></slot>
<view v-if="timingFun == 'cubic-bezier' && contentBgColor" :style="{background:contentBgColor}" :class="'content-bg-' + direction"></view>
</view>
</view>
</template>
<script setup>
import { reactive, defineProps, defineEmits, defineExpose, computed, onMounted, watch } from 'vue';
const controller = reactive({
open: false,
lock: false,
bg: false,
bgOpacity: 0,
translate: -100,
time: null
});
const emits = defineEmits(['onClose']);
const timingFuns = {
'cubic-bezier': 'transition-timing-function: cubic-bezier(.15,-0.88,.41,1.68);',
ease: 'transition-timing-function: easy;',
'ease-in': 'transition-timing-function: ease-in;',
'ease-out': 'transition-timing-function: ease-out;',
'ease-in-out': 'transition-timing-function: ease-in-out;',
linear: 'transition-timing-function: linear;'
};
const props = defineProps({
direction: {
type: String,
default: 'top' // 方向 top,bottom,left,right
},
bgOpacity: {
type: Number,
default: 0.38
},
timingFun: {
type: String,
default: 'cubic-bezier'
},
contentBgColor: {
type: String,
default: ''
},
transitionDuration: {
type: String,
default: '0.5s'
}
});
watch(() => props.timingFun, (newValue, oldValue) => {}, {
immediate: true
});
const _translate = computed(() => {
const transformObj = {
top: `transform:translateY(${-controller.translate}%)`,
bottom: `transform:translateY(${controller.translate}%)`,
left: `transform:translateX(${-controller.translate}%)`,
right: `transform:translateX(${controller.translate}%)`
};
return transformObj[props.direction];
});
const _location = computed(() => {
const positionValue = {
top: 'bottom:0px;width:100%;',
bottom: 'top:0px;width:100%;',
left: 'right:0px;top:0px;height:100%;',
right: 'left:0px;top:0px;height:100%;'
};
let style = `transition-duration:${props.transitionDuration};`;
if (timingFuns[props.timingFun]) {
style += timingFuns[props.timingFun];
}
return style + positionValue[props.direction] + _translate.value;
});
const open = function() {
if (controller.open) {
return;
}
controller.open = true;
let openTimer = setTimeout(() => {
clearTimeout(openTimer);
controller.lock = true;
controller.bg = true;
controller.bgOpacity = props.bgOpacity;
controller.translate = -0.1;
openTimer = null;
}, 100);
// 防止误触
let lockTimer = setTimeout(() => {
clearTimeout(lockTimer);
controller.lock = false;
lockTimer = null;
}, 500);
};
const close = function() {
if (controller.timer || controller.lock) {
return;
}
controller.bgOpacity = 0;
controller.bg = false;
controller.translate = -100;
controller.timer = setTimeout(() => {
clearTimeout(controller.timer);
controller.open = false;
controller.timer = null;
emits('onClose');
}, 500);
};
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.popup-layer {
width: 100vw;
height: 100vh;
overflow: hidden;
top: 0px;
left: 0px;
position: fixed;
z-index: 9999;
.popup-content {
position: absolute;
z-index: 1000000;
transition: transform;
}
.content-bg-top {
position: absolute;
width: 100%;
height: 108rpx;
left: 0;
bottom: -100rpx;
}
.content-bg-bottom {
position: absolute;
width: 100%;
height: 108rpx;
left: 0;
top: -100rpx;
}
.content-bg-left {
position: absolute;
width: 108rpx;
height: 100%;
top: 0;
right: -100rpx;
}
.content-bg-right {
position: absolute;
width: 108rpx;
height: 100%;
top: 0;
left: -100rpx;
}
}
// 底部出来
.top-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
top: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s top;
}
.top-bg-show {
top: 0;
}
.top-bg-hide {
top: 121vh;
}
.top-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
top: -30vw;
}
// 顶部出来
.bottom-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
bottom: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s bottom;
}
.bottom-bg-show {
bottom: 0;
}
.bottom-bg-hide {
bottom: 121vh;
}
.bottom-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
bottom: -30vw;
}
// 右边出来
.left-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
left: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.left-bg-show {
left: 0;
}
.left-bg-hide {
left: 121vw;
}
.left-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
left: -30vh;
}
// 左边边出来
.right-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
right: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.right-bg-show {
right: 0;
}
.right-bg-hide {
right: 121vw;
}
.right-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
right: -30vh;
}
</style>