Vue3中Teleport模态框显示有动画、消失无动画的问题求助
模态框显示/消失双向动画解决方案
方案一:将显示控制逻辑移入ModalWrapper组件内部
核心思路是把v-if从父组件移到ModalWrapper内部,让组件自主管理过渡动画的完整生命周期,通过Prop接收外部状态,过渡结束后再通知外部更新状态。
- 改造ModalWrapper组件:
- 添加
visibleProp,接收外部传入的显示状态 - 内部维护本地状态
isShowing,初始值与visible同步 - 用
watch监听visible变化,触发显示过渡 - 给
transition绑定@after-leave事件,过渡结束后更新本地状态并通知父组件
- 添加
<!-- ModalWrapper.vue --> <template> <Teleport to="body"> <transition name="modal" appear @after-leave="handleAfterLeave"> <div v-if="isShowing" class="modal-overlay"> <div class="modal-content"> <slot></slot> <button @click="handleClose">关闭</button> </div> </div> </transition> </Teleport> </template> <script setup> import { ref, watch } from 'vue' const props = defineProps({ visible: { type: Boolean, default: false } }) const emit = defineEmits(['update:visible']) const isShowing = ref(props.visible) watch(() => props.visible, (newVal) => { if (newVal) isShowing.value = true }) const handleClose = () => { isShowing.value = false } const handleAfterLeave = () => { emit('update:visible', false) } </script> <style scoped> .modal-enter-from, .modal-leave-to { opacity: 0; } .modal-enter-active, .modal-leave-active { transition: opacity 0.3s ease; } .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; } .modal-content { background: white; padding: 20px; border-radius: 4px; } </style>
- 父组件(Home.vue)使用方式:
移除v-if,通过v-model绑定Vuex状态变量
<!-- Home.vue --> <template> <button @click="openModal">打开模态框</button> <ModalWrapper v-model:visible="$store.state.modal.newModalVisible"> <h2>自定义模态框内容</h2> </ModalWrapper> </template> <script setup> import { useStore } from 'vuex' const store = useStore() const openModal = () => { store.commit('modal/setNewModalVisible', true) } </script>
方案二:通过Vuex状态订阅自动管理过渡
针对每个模态框有独立Vuex状态变量的场景,让ModalWrapper接收stateKey Prop,直接从Vuex获取状态并订阅变化,内部处理过渡逻辑,无需父组件额外控制。
<!-- ModalWrapper.vue --> <template> <Teleport to="body"> <transition name="modal" appear @after-leave="handleAfterLeave"> <div v-if="isShowing" class="modal-overlay"> <div class="modal-content"> <slot></slot> <button @click="closeModal">关闭</button> </div> </div> </transition> </Teleport> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue' import { useStore } from 'vuex' const props = defineProps({ stateKey: { type: String, required: true } }) const store = useStore() const isShowing = ref(false) let unsubscribe = null onMounted(() => { isShowing.value = store.state.modal[props.stateKey] // 订阅对应状态的变化 unsubscribe = store.subscribe((mutation) => { const mutationType = `modal/set${props.stateKey.charAt(0).toUpperCase() + props.stateKey.slice(1)}` if (mutation.type === mutationType && mutation.payload) { isShowing.value = true } }) }) onUnmounted(() => { unsubscribe?.() }) const closeModal = () => { isShowing.value = false } const handleAfterLeave = () => { const mutationType = `modal/set${props.stateKey.charAt(0).toUpperCase() + props.stateKey.slice(1)}` store.commit(mutationType, false) } </script>
- 父组件使用方式:
<!-- Home.vue --> <template> <button @click="openModal">打开模态框</button> <ModalWrapper state-key="newModalVisible"> <h2>自定义模态框内容</h2> </ModalWrapper> </template> <script setup> import { useStore } from 'vuex' const store = useStore() const openModal = () => { store.commit('modal/setNewModalVisible', true) } </script>
关键原理说明
原问题的核心是v-if在父组件时,状态变为false会直接销毁ModalWrapper,导致内部transition无法执行离开动画。将显示控制移到组件内部后,先通过isShowing = false触发离开动画,等动画完成后再更新外部状态,确保过渡效果完整执行。
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

