当前位置: 首页 > news >正文

使用 `<Teleport>` 实现全局模态框(Vue 3)

使用<Teleport>实现全局模态框(Vue 3)

<Teleport>是 Vue 3 内置组件,可以将组件模板的一部分“传送”到 DOM 中任意位置(如<body>),从而突破组件层级限制,非常适合实现模态框、全局提示、下拉菜单等需要脱离父容器样式的场景。

下面实现一个可复用、支持插槽、带有动画的全局模态框组件。


1. 模态框组件:GlobalModal.vue
<template> <!-- 使用 Teleport 将模态框传送到 body --> <Teleport to="body"> <!-- 遮罩层(点击关闭) --> <div v-if="modelValue" class="modal-overlay" @click.self="close"> <!-- 内容容器 --> <div class="modal-container" :class="{ 'modal-enter': isVisible }"> <div class="modal-content"> <!-- 关闭按钮 --> <button class="modal-close" @click="close">✕</button> <!-- 标题插槽 --> <div class="modal-header"> <slot name="header"> <h3>{{ title }}</h3> </slot> </div> <!-- 默认插槽:主体内容 --> <div class="modal-body"> <slot /> </div> <!-- 底部操作插槽 --> <div class="modal-footer" v-if="$slots.footer"> <slot name="footer" /> </div> </div> </div> </div> </Teleport> </template> <script setup> import { watch, nextTick, ref } from 'vue' const props = defineProps({ modelValue: { type: Boolean, default: false }, title: { type: String, default: '提示' }, // 点击遮罩是否关闭 closeOnClickOverlay: { type: Boolean, default: true }, // 是否显示动画(简单过渡) animated: { type: Boolean, default: true } }) const emit = defineEmits(['update:modelValue', 'close', 'open']) // 内部控制动画显示 const isVisible = ref(false) // 监听 modelValue 变化,控制打开/关闭动画 watch( () => props.modelValue, async (newVal) => { if (newVal) { // 打开时,先让元素显示,再触发动画 await nextTick() isVisible.value = true emit('open') // 禁止 body 滚动(避免穿透) document.body.style.overflow = 'hidden' } else { // 关闭动画 isVisible.value = false document.body.style.overflow = '' emit('close') } }, { immediate: true } ) const close = () => { if (props.closeOnClickOverlay) { emit('update:modelValue', false) } } </script> <style scoped> /* 遮罩 */ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; animation: fadeIn 0.3s ease; } /* 内容容器(带动画) */ .modal-container { background: white; border-radius: 12px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); transform: scale(0.8); opacity: 0; transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; } .modal-container.modal-enter { transform: scale(1); opacity: 1; } /* 内容内边距 */ .modal-content { padding: 24px; position: relative; } /* 关闭按钮 */ .modal-close { position: absolute; top: 12px; right: 16px; background: none; border: none; font-size: 22px; line-height: 1; cursor: pointer; color: #999; transition: color 0.2s; } .modal-close:hover { color: #333; } /* 标题 */ .modal-header { margin-bottom: 16px; padding-right: 30px; } .modal-header h3 { margin: 0; font-size: 18px; } /* 主体 */ .modal-body { margin-bottom: 20px; } /* 底部 */ .modal-footer { display: flex; justify-content: flex-end; gap: 10px; border-top: 1px solid #eee; padding-top: 16px; } /* 进入动画(遮罩淡入) */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } </style>

2. 父组件使用示例
<template> <div> <button @click="openModal">打开模态框</button> <!-- 使用模态框,通过 v-model 控制显示 --> <GlobalModal v-model="showModal" title="温馨提示"> <!-- 默认插槽:主体内容 --> <p>这是模态框的内容区域,可以放置任何 Vue 组件或 HTML。</p> <input v-model="inputValue" placeholder="输入一些内容..." /> <p>输入的值:{{ inputValue }}</p> <!-- 底部插槽:自定义按钮 --> <template #footer> <button class="btn-primary" @click="confirm">确认</button> <button class="btn-secondary" @click="showModal = false">取消</button> </template> </GlobalModal> </div> </template> <script setup> import { ref } from 'vue' import GlobalModal from './GlobalModal.vue' const showModal = ref(false) const inputValue = ref('') const openModal = () => { showModal.value = true } const confirm = () => { alert(`确认操作,输入内容:${inputValue.value}`) showModal.value = false } </script> <style> .btn-primary { background: #42b883; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } .btn-secondary { background: #eee; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } </style>

3. 关键技术与注意事项
技术点实现方式说明
传送目标<Teleport to="body">模态框 DOM 将被挂载到<body>下,脱离父组件样式和定位限制。
双向绑定v-model="showModal"组件内通过modelValueprop 和update:modelValue事件实现。
遮罩点击关闭@click.self="close"只有点击遮罩本身(而非其子元素)才触发关闭。
防止滚动穿透document.body.style.overflow = 'hidden'模态框打开时禁用 body 滚动,关闭时恢复。
过渡动画使用 CSS transition + 动态 class通过内部isVisible控制类名,实现平滑打开/关闭。
插槽灵活提供headerdefaultfooter插槽父组件可完全自定义标题、内容和底部按钮。
多个模态框可同时使用多个<GlobalModal>每个实例独立控制,但注意 z-index 堆叠。
http://www.cnnetsun.cn/news/3013313.html

相关文章:

  • 高仕星维生素B能稳固发根吗
  • 数字孪生+大模型重塑工业产线管控模式
  • Java计算机毕设之基于SpringBoot+Vue的校园潮流音乐在线播放平台设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • 【课程设计/毕业设计】基于SpringBoot的在线潮流音乐视听服务系统设计与实现 个性化歌单创建与潮流音乐播放系统设计与实现【附源码、数据库、万字文档】
  • PHP开发者的福音!这套开源商城源码,堪称二开界的“瑞士军刀”!
  • Windows、Android、iOS 各自的伟大之处
  • 【计算机毕业设计案例】基于 SpringBoot+Vue 的财务报表生成管理系统设计与实现 中小企业财会业务信息化管理系统设计与实现(程序+文档+讲解+定制)
  • IntelliJ IDEA旗舰版安装失败诊断手册(93%用户卡在第4步!含JetBrains License Server 2024.1.3实测绕过方案)
  • 【船舶】船舶操纵性MMG方程matlab实现,适用于船舶操纵轨迹预测
  • Gemini 3 Pro提示词工程与自动化工作流实战指南
  • 【C/C++】select、poll、epoll 实战对比:从 fd_set 到就绪事件列表
  • 视频 API 接口是什么?给小白的一篇入门指南
  • 分布式爬虫中的任务调度策略深度剖析
  • Sunshine开源游戏串流服务器:跨平台自托管流媒体技术深度解析
  • 提“效”利器!桥田修磨机全系列重磅升级,高寿命刀具惊艳亮相!
  • 优学宝知识付费与在线刷题系统全新上线,兼容 Word、Excel 题库一键导入,内置 AI 大模型实现试题智能识别分类。系统对注册用户免费开放,涵盖上传与分享、协同刷题、全真模拟测验、多题型分类练。
  • Ubuntu 无线网络链接
  • Python量化交易数据获取的终极解决方案:efinance免费金融数据库完全指南 [特殊字符]
  • PyTorch KernelAgent 源码解读 ---(1)--- 原理
  • 【LLM系列】FlashAttention V3 深度解析:把H100算力利用率从35%拉到75%的秘密
  • Cahn-Hilliard与Keller-Segel耦合模型的弱解存在性与唯一性分析
  • 新型公共文化空间智能化设备深度解析:4个核心维度,选对靠谱升级方案
  • 别再只会问 Claude 了:搞懂工具调用,才算真正用明白 Claude 3
  • 大模型聚合 API 全网测速实测:延迟瓶颈拆解与商用平台落地对比
  • 如何高效使用智能屏幕翻译工具:终极操作指南
  • Windows FRP 内网穿透完整教程:从零搭建到实战应用
  • 2026新版PMP:技术岗值得考吗?涨薪攻略+避坑指南
  • Spring Boot + MyBatis 多模块项目中,如何优雅完成一个增量需求
  • 基于51单片机的智能香薰灯:从PID温控到WS2812B灯效的嵌入式开发实践
  • Spring Boot 跨服务事务实现