Teleport与过渡动画 #

一、Teleport组件 #

1.1 基本使用 #

Teleport可以将组件渲染到DOM树的其他位置。

vue
<template>
  <div class="container">
    <button @click="showModal = true">打开模态框</button>
    
    <Teleport to="body">
      <div v-if="showModal" class="modal">
        <div class="modal-content">
          <h2>模态框</h2>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>

1.2 Teleport属性 #

vue
<template>
  <!-- to: 目标选择器 -->
  <Teleport to="body">
    <div>渲染到body</div>
  </Teleport>
  
  <!-- to: 目标DOM元素 -->
  <Teleport :to="targetElement">
    <div>渲染到指定元素</div>
  </Teleport>
  
  <!-- disabled: 禁用传送 -->
  <Teleport to="body" :disabled="isMobile">
    <div>移动端不传送</div>
  </Teleport>
</template>

1.3 实际应用:模态框组件 #

vue
<!-- Modal.vue -->
<template>
  <Teleport to="body">
    <Transition name="modal">
      <div v-if="modelValue" class="modal-overlay" @click.self="close">
        <div class="modal-container">
          <div class="modal-header">
            <h3>{{ title }}</h3>
            <button class="close-btn" @click="close">&times;</button>
          </div>
          <div class="modal-body">
            <slot></slot>
          </div>
          <div class="modal-footer" v-if="$slots.footer">
            <slot name="footer"></slot>
          </div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup>
defineProps({
  modelValue: Boolean,
  title: String
})

const emit = defineEmits(['update:modelValue'])

function close() {
  emit('update:modelValue', false)
}
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-container {
  background: white;
  border-radius: 8px;
  max-width: 500px;
  width: 90%;
  max-height: 90vh;
  overflow: auto;
}

.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.3s ease;
}

.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}
</style>

二、过渡动画 #

2.1 Transition组件 #

vue
<template>
  <button @click="show = !show">切换</button>
  
  <Transition>
    <p v-if="show">过渡内容</p>
  </Transition>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>

<style>
/* 进入动画 */
.v-enter-active {
  transition: all 0.3s ease;
}

/* 离开动画 */
.v-leave-active {
  transition: all 0.3s ease;
}

/* 进入开始状态 */
.v-enter-from {
  opacity: 0;
  transform: translateX(-20px);
}

/* 离开结束状态 */
.v-leave-to {
  opacity: 0;
  transform: translateX(20px);
}
</style>

2.2 自定义过渡类名 #

vue
<template>
  <Transition
    name="fade"
    enter-active-class="animate__animated animate__fadeIn"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <p v-if="show">动画内容</p>
  </Transition>
</template>

2.3 JavaScript钩子 #

vue
<template>
  <Transition
    @before-enter="onBeforeEnter"
    @enter="onEnter"
    @after-enter="onAfterEnter"
    @enter-cancelled="onEnterCancelled"
    @before-leave="onBeforeLeave"
    @leave="onLeave"
    @after-leave="onAfterLeave"
    @leave-cancelled="onLeaveCancelled"
  >
    <div v-if="show">动画元素</div>
  </Transition>
</template>

<script setup>
function onBeforeEnter(el) {
  el.style.opacity = 0
}

function onEnter(el, done) {
  // 动画结束后调用done
  el.offsetHeight // 触发重排
  el.style.transition = 'opacity 0.5s'
  el.style.opacity = 1
  el.addEventListener('transitionend', done)
}

function onAfterEnter(el) {
  console.log('进入动画完成')
}

function onBeforeLeave(el) {
  el.style.opacity = 1
}

function onLeave(el, done) {
  el.style.transition = 'opacity 0.5s'
  el.style.opacity = 0
  el.addEventListener('transitionend', done)
}

function onAfterLeave(el) {
  console.log('离开动画完成')
}
</script>

2.4 可复用过渡 #

vue
<!-- FadeTransition.vue -->
<template>
  <Transition name="fade" mode="out-in">
    <slot></slot>
  </Transition>
</template>

<style scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
vue
<template>
  <FadeTransition>
    <div :key="currentPage">
      {{ currentPage }}
    </div>
  </FadeTransition>
</template>

三、列表过渡 #

3.1 TransitionGroup #

vue
<template>
  <button @click="addItem">添加</button>
  <button @click="removeItem">移除</button>
  
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </TransitionGroup>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, text: '项目1' },
  { id: 2, text: '项目2' },
  { id: 3, text: '项目3' }
])

let nextId = 4

function addItem() {
  items.value.push({ id: nextId++, text: `项目${nextId - 1}` })
}

function removeItem() {
  items.value.pop()
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

/* 移动动画 */
.list-move {
  transition: transform 0.5s ease;
}
</style>

3.2 列表排序动画 #

vue
<template>
  <button @click="shuffle">打乱</button>
  
  <TransitionGroup name="shuffle" tag="div" class="container">
    <div v-for="item in items" :key="item.id" class="item">
      {{ item.text }}
    </div>
  </TransitionGroup>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, text: 'A' },
  { id: 2, text: 'B' },
  { id: 3, text: 'C' },
  { id: 4, text: 'D' }
])

function shuffle() {
  items.value = items.value.sort(() => Math.random() - 0.5)
}
</script>

<style>
.container {
  display: flex;
  flex-wrap: wrap;
}

.item {
  width: 50px;
  height: 50px;
  margin: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #42b983;
  color: white;
}

.shuffle-move {
  transition: transform 0.5s ease;
}
</style>

四、过渡模式 #

4.1 in-out模式 #

vue
<template>
  <Transition name="fade" mode="in-out">
    <div :key="current" class="box">
      {{ current }}
    </div>
  </Transition>
</template>

<style>
/* 先进入再离开 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

4.2 out-in模式 #

vue
<template>
  <Transition name="fade" mode="out-in">
    <div :key="current" class="box">
      {{ current }}
    </div>
  </Transition>
</template>

<style>
/* 先离开再进入 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

五、常见过渡效果 #

5.1 滑动过渡 #

vue
<style>
.slide-enter-active,
.slide-leave-active {
  transition: transform 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
}

.slide-leave-to {
  transform: translateX(-100%);
}
</style>

5.2 缩放过渡 #

vue
<style>
.scale-enter-active,
.scale-leave-active {
  transition: all 0.3s ease;
}

.scale-enter-from,
.scale-leave-to {
  opacity: 0;
  transform: scale(0.9);
}
</style>

5.3 折叠过渡 #

vue
<template>
  <button @click="expanded = !expanded">切换</button>
  
  <Transition name="collapse">
    <div v-show="expanded" class="content">
      折叠内容
    </div>
  </Transition>
</template>

<style>
.collapse-enter-active,
.collapse-leave-active {
  transition: all 0.3s ease;
  overflow: hidden;
}

.collapse-enter-from,
.collapse-leave-to {
  max-height: 0;
  opacity: 0;
}

.content {
  max-height: 200px;
}
</style>

六、总结 #

Teleport #

属性 说明
to 目标选择器或DOM元素
disabled 是否禁用传送

Transition类名 #

类名 说明
v-enter-from 进入开始状态
v-enter-active 进入过渡效果
v-enter-to 进入结束状态
v-leave-from 离开开始状态
v-leave-active 离开过渡效果
v-leave-to 离开结束状态

过渡动画要点:

  • Teleport用于跨层级渲染
  • Transition用于单个元素过渡
  • TransitionGroup用于列表过渡
  • 使用mode控制进入/离开顺序
  • 可复用过渡组件封装动画
最后更新:2026-03-26