Nuxt3 手写一个全局弹窗 (Modal)

本文由 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 或者有其它问题,也欢迎在评论区提出来。

如果这篇文章有帮到你,还请点一个不要钱的赞,感谢支持 ❤️

相关推荐
Amd79418 小时前
Nuxt.js 应用中的 app:mounted 钩子详解
生命周期·nuxt.js·钩子函数·组件渲染·dom操作·vue应用·app:mounted
天涯学馆3 天前
Vue.js与Nuxt.js:动态路由与中间件
前端·vue.js·nuxt.js
Amd7943 天前
Nuxt.js 应用中的 app:redirected 钩子详解
认证·日志·nuxt.js·ssr·重定向·钩子·示例
Amd7944 天前
Nuxt.js 应用中的 app:rendered 钩子详解
生命周期·nuxt.js·服务器渲染·日志记录·性能监控·钩子函数·ssr优化
Amd7947 天前
深入理解 Nuxt.js 中的 app:data:refresh 钩子
前端开发·nuxt.js·钩子函数·代码示例·数据刷新·ui优化·动态更新
Amd7948 天前
深入理解 Nuxt.js 中的 app:error:cleared 钩子
生命周期·nuxt.js·错误处理·应用开发·钩子·用户反馈·状态恢复
程序员长夜8 天前
nuxtjs学习-路由
前端·nuxt.js
Amd7949 天前
深入理解 Nuxt.js 中的 app:error 钩子
前端框架·nuxt.js·用户体验·错误处理·应用开发·钩子函数·代码示例
麻爪10 天前
Nuxt 常用配置和生命周期钩子的使用
vue.js·nuxt.js
熊猫在哪14 天前
安装nuxt3
前端·nuxt.js