开发中经常会遇到弹窗显示的情况,在当前页面进行更多操作或提示而不离开本页面,这时就是弹窗大显身手的时候了,开发过程中使用多的还是组件库中已经封装好的弹窗,最近在开发的时候突然对弹窗有了兴趣,想要更加深入了解一下。
写弹窗肯定无法离开遮罩,咱们就先来实现一下遮罩。
遮罩的实现
遮罩我们肯定是想显示在最上层,所有需要将遮罩元素添加到最外层元素中,vue3
中我们可以使用 teleport
元素将元素添加到目标元素中,to
属性就是我们的目标元素,我们也可以在遮罩中添加一个新的 props
可以叫做 target
指向我们的目标元素。react
使用的是 Portals
整体使用也是跟teleport
类似。
遮罩展示后,通常情况下都有滚动穿透的问题,这里我在遮罩展示时添加 lock
类,只是简单的一个 overflow: hidden !important;
阻止 body
元素的滚动,并且我添加了一个 touchmove
事件 @touchmove="$event.preventDefault()"
阻止手势滑动,但是我使用的方法过于简单,如果要求不高的话可以使用。
滚动穿透更详细的解决方案可以参考像
vueuse
useScrollLock 方法或者ant-design-mobile
use-lock-scroll
<template>
<teleport to="body">
<div
class="overlay-wrapper"
:class="{ centered }"
:style="{ ...maskStyled, display: isShowing ? (centered ? 'flex' : 'block') : 'none', opacity }"
@touchmove="$event.preventDefault()"
@click="onOverlayClick"
>
<slot />
</div>
</teleport>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import type { CSSProperties } from 'vue';
const props = withDefaults(
defineProps<{
// 是否显示遮罩
show: boolean;
// 是否需要锁定页面滚动
needLock: boolean;
// 遮罩中的元素是否居中显示
centered: boolean;
// 是否要在点击遮罩时关闭遮罩
closeOnClickOverlay: boolean;
// 自定义遮罩样式
maskStyled: CSSProperties;
}>(),
{ show: false, needLock: true, closeOnClickOverlay: true, maskStyled: () => ({}) }
);
const emit = defineEmits(['close']);
// 设置遮罩的透明度
const opacity = ref(0);
const isShowing = ref(props.show);
// 监听遮罩的显示和隐藏
watch(
() => props.show,
(value) => {
if (value) {
if (props.needLock) {
document.body.classList.add('lock');
}
isShowing.value = true;
// 使用 setTimeout 是为了延迟更改 opacity,为了让动画能够正常显示,如果直接更改,由于 display: none 改变后会直接显示元素而不会触发动画
setTimeout(() => {
opacity.value = 1;
});
} else {
if (props.needLock) {
document.body.classList.remove('lock');
}
opacity.value = 0;
// 消失动画结束后再将 isShowing 设置为 false,直接更改会立即消失
setTimeout(() => {
isShowing.value = false;
}, 200);
}
}
);
const onOverlayClick = (e: MouseEvent) => {
// 如果当前点击的元素是遮罩层元素,就要关闭遮罩
if (props.closeOnClickOverlay && e.target === e.currentTarget) {
emit('close', false);
}
};
</script>
<style scoped lang="less">
.overlay-wrapper {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
position: fixed;
top: 0;
opacity: 0;
transition: all 0.2s ease-in-out;
}
.centered {
justify-content: center;
align-items: center;
}
</style>
弹窗的实现
遮罩实现之后,我们的弹窗实现就简单了,只要在遮罩的基础上加上我们的弹出层即可。
弹窗中主要注意出现时机与动画
<template>
<Overlay :show="show" :centered="mode === 'center'">
<div class="drawer-wrapper" :class="[currentModeClass, showClass, className]">
<slot />
</div>
</Overlay>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
// 弹窗 className
className?: string;
show?: boolean;
// 弹窗展开方向
mode?: 'top' | 'bottom' | 'left' | 'right' | 'center';
// 弹窗样式
}>(),
{ show: false, mode: 'bottom', className: '' }
);
const currentModeClass = computed(() => {
return { top: 'top', bottom: 'bottom', left: 'left', right: 'right', center: 'center' }[props.mode];
});
const showClass = ref<string>('');
watch(
() => props.show,
(value) => {
if (value) {
setTimeout(() => {
showClass.value = `${currentModeClass.value}-active`;
});
} else {
showClass.value = '';
}
}
);
</script>
<style scoped lang="less">
.drawer-wrapper {
overflow: auto;
background-color: #fff;
color: #000;
position: absolute;
transition: all 0.2s ease-in-out;
}
.bottom {
width: 100%;
max-height: 60%;
bottom: 0;
transform: translateY(100%);
}
.bottom-active {
transform: none;
}
.top {
width: 100%;
max-height: 60%;
top: 0;
transform: translateY(-100%);
}
.top-active {
transform: none;
}
.left {
height: 100%;
max-width: 70%;
left: 0;
transform: translateX(-100%);
}
.left-active {
transform: none;
}
.right {
height: 100%;
max-width: 70%;
right: 0;
transform: translateX(100%);
}
.right-active {
transform: none;
}
.center {
width: 80%;
height: 60%;
transform: scale(0);
}
.center-active {
transform: scale(1);
}
</style>
更多
html
提供了一个 dialog:对话框元素 – HTML(超文本标记语言) | MDN (mozilla.org) 实际我们可以直接使用
原文链接:https://juejin.cn/post/7319297259644289058 作者:过期也候