You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中Teleport模态框显示有动画、消失无动画的问题求助

模态框显示/消失双向动画解决方案

方案一:将显示控制逻辑移入ModalWrapper组件内部

核心思路是把v-if从父组件移到ModalWrapper内部,让组件自主管理过渡动画的完整生命周期,通过Prop接收外部状态,过渡结束后再通知外部更新状态。

  • 改造ModalWrapper组件:
    1. 添加visible Prop,接收外部传入的显示状态
    2. 内部维护本地状态isShowing,初始值与visible同步
    3. 用watch监听visible变化,触发显示过渡
    4. 给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:38:13