使用Teleport实现全局模态框Vue 3Teleport是 Vue 3 内置组件可以将组件模板的一部分“传送”到 DOM 中任意位置如body从而突破组件层级限制非常适合实现模态框、全局提示、下拉菜单等需要脱离父容器样式的场景。下面实现一个可复用、支持插槽、带有动画的全局模态框组件。1. 模态框组件GlobalModal.vuetemplate !-- 使用 Teleport 将模态框传送到 body -- Teleport tobody !-- 遮罩层点击关闭 -- div v-ifmodelValue classmodal-overlay click.selfclose !-- 内容容器 -- div classmodal-container :class{ modal-enter: isVisible } div classmodal-content !-- 关闭按钮 -- button classmodal-close clickclose✕/button !-- 标题插槽 -- div classmodal-header slot nameheader h3{{ title }}/h3 /slot /div !-- 默认插槽主体内容 -- div classmodal-body slot / /div !-- 底部操作插槽 -- div classmodal-footer v-if$slots.footer slot namefooter / /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; } } /style2. 父组件使用示例template div button clickopenModal打开模态框/button !-- 使用模态框通过 v-model 控制显示 -- GlobalModal v-modelshowModal title温馨提示 !-- 默认插槽主体内容 -- p这是模态框的内容区域可以放置任何 Vue 组件或 HTML。/p input v-modelinputValue placeholder输入一些内容... / p输入的值{{ inputValue }}/p !-- 底部插槽自定义按钮 -- template #footer button classbtn-primary clickconfirm确认/button button classbtn-secondary clickshowModal 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; } /style3. 关键技术与注意事项技术点实现方式说明传送目标Teleport tobody模态框 DOM 将被挂载到body下脱离父组件样式和定位限制。双向绑定v-modelshowModal组件内通过modelValueprop 和update:modelValue事件实现。遮罩点击关闭click.selfclose只有点击遮罩本身而非其子元素才触发关闭。防止滚动穿透document.body.style.overflow hidden模态框打开时禁用 body 滚动关闭时恢复。过渡动画使用 CSS transition 动态 class通过内部isVisible控制类名实现平滑打开/关闭。插槽灵活提供header、default、footer插槽父组件可完全自定义标题、内容和底部按钮。多个模态框可同时使用多个GlobalModal每个实例独立控制但注意 z-index 堆叠。