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

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

相关推荐
JXDN23 天前
Nuxt.js代码风格配置
vue.js·nuxt.js
Amd79423 天前
Nuxt.js 应用中的 modules:done 事件钩子详解
生命周期·前端开发·nuxt.js·钩子函数·代码示例·应用初始化·modules:done
Amd79424 天前
Nuxt.js 应用中的 modules:before 事件钩子详解
生命周期·配置·模块·nuxt.js·初始化·钩子·环境设置
熊猫在哪25 天前
nuxt3脚手架安装报错解决方法
前端·javascript·nuxt.js
Amd79425 天前
Nuxt.js 应用中的 restart 事件钩子详解
nuxt.js·实例方法·开发技巧·普通重启·硬重启·应用重启·重启方法
Amd7941 个月前
Nuxt.js 应用中的 close 事件钩子详解
生命周期·nuxt.js·日志记录·状态保存·资源清理·应用关闭·close钩子
Amd7941 个月前
Nuxt.js 应用中的 ready 事件钩子详解
生命周期·前端开发·nuxt.js·请求处理·应用初始化·nuxt实例·ready钩子
Amd7941 个月前
Nuxt.js 应用中的 kit:compatibility 事件钩子详解
浏览器·开发·应用·nuxt.js·插件·兼容性·钩子
Amd7941 个月前
Nuxt.js 应用中的 page:transition:finish 钩子详解
前端开发·nuxt.js·状态管理·钩子函数·ui更新·页面动画·页面过渡
Amd7941 个月前
Nuxt.js 应用中的 page:finish 钩子详解
nuxt.js·性能分析·用户体验·钩子·suspense·状态更新·page:finish