Appearance
模态框
记录了一次封装模态框的代码。
vue
<template>
<Teleport to="body">
<Transition name="modal">
<div
class="modal-container"
v-if="isShowModal"
@click="handleCloseModal">
<Transition
name="content"
appear>
<div
@click.stop=""
class="content"
v-if="isShow">
<slot name="default"></slot>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from "vue";
const props = defineProps<{
/**
* 模态框是否显示
*/
modelValue: boolean;
}>();
const emit = defineEmits<{
/**
*
*/
"update:modelValue": [value: boolean];
/**
* 模态框关闭的事件
*/
close: [];
}>();
defineSlots<{
default: () => any;
}>();
// 是否显示主要内容
const isShow = ref(true);
const isShowModal = ref(false);
// 当前是否还在执行关闭的动画效果?
let time: null | number = null;
/**
* 点击遮罩层的回调
*/
const handleCloseModal = () => {
isShow.value = false;
// 动画效果执行完毕时 销毁模态框
nextTick(() => {
time = setTimeout(() => {
isShowModal.value = false;
emit("update:modelValue", false);
emit("close");
time = null;
}, 300);
});
};
// 保持一致
watch(
() => props.modelValue,
(v) => {
if (time) {
// 非常重要
// 若当前还有在挂起的延时器 则关闭延时器 避免数据源混乱
clearTimeout(time);
}
if (!v) {
// 关闭模态框
handleCloseModal();
} else {
isShowModal.value = v;
isShow.value = v;
}
},
{ immediate: true }
);
defineOptions({ name: "Modal" });
</script>
<style scoped lang="scss">
.modal-container {
position: fixed;
z-index: 999;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: var(--color-mask-bg);
display: flex;
align-items: center;
justify-content: center;
.content {
background-color: var(--color-bg-1);
min-height: 100px;
min-width: 100px;
}
.content-enter-active {
animation: contentMove 0.3s 1 ease-in-out;
}
.content-leave-active {
animation: contentMove 0.3s 1 ease-in-out reverse;
}
@keyframes contentMove {
from {
opacity: 0;
transform: scale(0.7);
}
to {
opacity: 1;
transform: none;
}
}
}
</style>
<template>
<Teleport to="body">
<Transition name="modal">
<div
class="modal-container"
v-if="isShowModal"
@click="handleCloseModal">
<Transition
name="content"
appear>
<div
@click.stop=""
class="content"
v-if="isShow">
<slot name="default"></slot>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from "vue";
const props = defineProps<{
/**
* 模态框是否显示
*/
modelValue: boolean;
}>();
const emit = defineEmits<{
/**
*
*/
"update:modelValue": [value: boolean];
/**
* 模态框关闭的事件
*/
close: [];
}>();
defineSlots<{
default: () => any;
}>();
// 是否显示主要内容
const isShow = ref(true);
const isShowModal = ref(false);
// 当前是否还在执行关闭的动画效果?
let time: null | number = null;
/**
* 点击遮罩层的回调
*/
const handleCloseModal = () => {
isShow.value = false;
// 动画效果执行完毕时 销毁模态框
nextTick(() => {
time = setTimeout(() => {
isShowModal.value = false;
emit("update:modelValue", false);
emit("close");
time = null;
}, 300);
});
};
// 保持一致
watch(
() => props.modelValue,
(v) => {
if (time) {
// 非常重要
// 若当前还有在挂起的延时器 则关闭延时器 避免数据源混乱
clearTimeout(time);
}
if (!v) {
// 关闭模态框
handleCloseModal();
} else {
isShowModal.value = v;
isShow.value = v;
}
},
{ immediate: true }
);
defineOptions({ name: "Modal" });
</script>
<style scoped lang="scss">
.modal-container {
position: fixed;
z-index: 999;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: var(--color-mask-bg);
display: flex;
align-items: center;
justify-content: center;
.content {
background-color: var(--color-bg-1);
min-height: 100px;
min-width: 100px;
}
.content-enter-active {
animation: contentMove 0.3s 1 ease-in-out;
}
.content-leave-active {
animation: contentMove 0.3s 1 ease-in-out reverse;
}
@keyframes contentMove {
from {
opacity: 0;
transform: scale(0.7);
}
to {
opacity: 1;
transform: none;
}
}
}
</style>