本文由 Loui 原创,如需转载,请先私信或评论。
背景
最近在做一个仿腾讯课堂的全栈开源项目,其中登录业务需要用到全局弹窗,因此自己做了一个,下面是最终效果和实现步骤,供大家参考、批评。
最终效果
我这里使用的是 Nuxt3 ,具体实现的代码和使用 Vue3 是差不多的。
此外,我还用到了以下两个内置组件:
名称 | 作用 |
---|---|
<Teleport> |
将组件的 DOM 结构转移到 <body> 下 |
<Transition> |
给蒙层和弹窗体添加出入场动画 |
这张截图来自我正在做的仿腾讯课堂全栈开源项目,项目前端使用 Nuxt3,后端使用 NestJS。项目还未完成,如果你对这个项目感兴趣的话,可以关注我,完成后我会把开源的 Github 地址发出来。
实现步骤
第一步:封装弹窗组件
将蒙层和弹窗体分别作为两个 div 封装到一个组件中,使用响应式变量 isOpened 同时控制两个 div 的 v-show 。同时创建 open 和 close 两个控制弹窗显示隐藏方法,并使用 defineExpose 导出。
vue
<script setup lang="ts">
const isOpened = ref(false)
// 关闭弹窗的方法
const close = function () {
isOpened.value = false
}
// 开启弹窗的方法
// 这里准备了一个 opt 形参,用于开启弹窗时传入一些数据,你可以根据业务需要修改
const open = (opt?: any) => {
isOpened.value = true
}
// 导出这两个方法
defineExpose({
open,
close,
})
</script>
<template>
<teleport to="body">
// 蒙层,已使用 Transition 包起来了,用于做出入场动画
// 注意:这里设置了点击蒙层也会关闭弹窗
<Transition name="mask">
<div
v-if="isOpened"
class="mask"
@click="close"
/>
</Transition>
// 弹窗体,已使用 Transition 包起来了,用于做出入场动画
<Transition name="modal">
<div v-if="isOpened" class="modal">
本文由 Loui 原创,首发于掘金,如需转载,请先私信或评论。
</div>
</Transition>
</teleport>
</template>
<style lang="scss" scoped>
.mask {
position: fixed;
inset-inline: 0 0;
top: 0;
bottom: 0;
z-index: 9998;
height: 100%;
background-color: rgb(0 0 0 / 45%);
}
.modal {
position: fixed;
top: 50%;
left: 50%;
z-index: 9999;
width: 440px;
min-height: 440px;
padding: 20px 24px;
background-color: #fff;
border-radius: 12px;
box-shadow:
0 6px 16px 0 rgb(0 0 0 / 8%),
0 3px 6px -4px rgb(0 0 0 / 12%),
0 9px 28px 8px rgb(0 0 0 / 5%);
transition: all 0.3s ease-in-out;
transform: translate(-50%, -50%);
}
// 弹窗体的出入场动画:
.modal-enter-active,
.modal-leave-active {
transition: all 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
transform: translate(-50%, -200%) scale(0);
}
// 蒙层的出入场动画:
.mask-enter-active,
.mask-leave-active {
transition: opacity 0.3s ease;
}
.mask-enter-from,
.mask-leave-to {
opacity: 0;
}
</style>
第二步:在顶层页面引入该组件,并使用 provide 注入它
在顶层页面中,比如说 App.vue 、layouts/default.vue 里,引入该弹窗组件,并使用 provide 方法,将该组件的模板引用提供给所有的子页面、子组件。
我这里是在 layouts/default.vue 里引入了该弹窗组件:
vue
<script lang="ts" setup>
const loginModal = ref<any>(null) // 获取弹窗组件的模板引用
provide('loginModal', loginModal) // 提供给所有的子页面、子组件
</script>
<template>
<MyHeader>
<slot />
<MyFooter />
<LoginModal ref="loginModal" />
</template>
第三步:在需要使用弹窗的页面,使用 inject 接收它
vue
<script lang="ts" setup>
// 注意:如果要支持 ts,这里一定要记得将组件的类型导入进来,Nuxt 不会自动引入
import type LoginModal from '@/components/LoginModal.vue'
// 使用 inject 方法,接收弹窗的模板引用,同时使用类型断言 as 设置它的类型
const loginModal = inject('loginModal') as InstanceType<typeof LoginModal>
</script>
<template>
<div class="login" @click="loginModal.open()">
登录
</div>
</template>
总结
这套代码其实还有很多可以完善的地方,如果你有其他更好的实现方法或者思路,欢迎在评论区补充。如果你发现了什么 Bug 或者有其它问题,也欢迎在评论区提出来。
如果这篇文章有帮到你,还请点一个不要钱的赞,感谢支持 ❤️