六维教程

Vue3 过渡动画

美观的页面都不是冰冷的突然出现/突然消失的,一般都有”过渡”、”切换”的动画,弹窗淡入淡出、列表项滑入滑出、标签页切换的位移,这些都是过渡动画

Vue 内置两个组件帮我们省掉大把 JS:<Transition>(单元素进出场)和 <TransitionGroup>(列表项增删移动)。

<Transition>

给要动画的元素套上 <Transition>(只能包一个元素),模板部分很普通,就是个按钮配 v-if 切换:

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

const show = ref(true)
</script>

<template>
  <button @click="show = !show">切换显示</button>

  <Transition>
    <p v-if="show">这段文字会淡入淡出</p>
  </Transition>
</template>

动画效果藏在 CSS 里,<Transition> 会监控内部元素的插入/移除(由 v-ifv-show、动态组件触发),并在合适的时机自动给元素加上 v-enter-*v-leave-* 类,我们只需要声明过渡前状态和过渡期间的行为:

/* 整个过渡期间:给透明度变化设置时长和缓动 */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.3s ease;
}
/* 过渡前的起点:刚进入时和要离开时,先把透明度设为 0 */
.v-enter-from,
.v-leave-to {
  opacity: 0;
}

<Transition> 不能直接包多个,可以用 <template v-if="..."> 把元素组包起来;真正要”多个并行”的进出场,用多个 <Transition>,或直接用后面的 <TransitionGroup>

动画结束后想接着做点事,可以用 @after-enter / @after-leave 等钩子,它们在 class 过渡结束后触发,适合做收尾处理(如展开面板后滚动定位),这类场景用得不多,遇到再查文档即可。

动画类名的规律

默认类名前缀是 v-,一共 6 个类,成对出现:

类名 时机
v-enter-from 进入开始:元素刚被插入
v-enter-active 进入过程:整个进入阶段的过渡效果
v-enter-to 进入结束:目标状态
v-leave-from 离开开始
v-leave-active 离开过程
v-leave-to 离开结束(元素随后被移除)

<Transition> 一个 name,类名前缀就从 v- 变成名字:

<Transition name="fade">
  <p v-if="show">淡入淡出</p>
</Transition>

<style scoped>
/* 注意前缀随 name 变成 fade- */
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>

包了 <Transition> 却看不到动画,八成是忘了写 CSS(transition 那几条),或 name 改了但 CSS 前缀没跟着改,或里面不是一个元素/组件(多个元素要外加一层)。

排查时先把 name 去掉,让类名回到默认的 v- 前缀,这样”前缀对不上”这个坑就没了,要是还没动画,基本就剩”忘了写 CSS”或”不是单个元素”。

控制进入/离开顺序:mode

默认”同时进出”可能出现瞬间两个元素叠着(旧先离、新又入)。想要”旧的先走光、新的再进来”,用 mode="out-in"

<Transition name="fade" mode="out-in">
  <p v-if="show">左边内容</p>
  <p v-else>右边内容</p>
</Transition>

注意这也解决了”<Transition> 内 v-if 替换的两个元素”只能出现一个的问题。

<TransitionGroup>

列表增删也要动起来,用 <TransitionGroup>,它渲染一个真实容器(默认 span),可以配 tag 改成渲染 ul 等,给出现的项加动画:

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

const list = ref([{ id: 1, name: '苹果' }, { id: 2, name: '香蕉' }])
const addItem = () => list.value.push({ id: Date.now(), name: '橙子' })
const removeItem = (id) => list.value = list.value.filter(i => i.id !== id)
</script>

<template>
  <button @click="addItem">加一项</button>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in list" :key="item.id">
      {{ item.name }}
      <button @click="removeItem(item.id)">删除</button>
    </li>
  </TransitionGroup>
</template>

<style scoped>
.list-enter-active, .list-leave-active { transition: all 0.3s ease; }
.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(20px); }
</style>

两个细节:

  • 列表项必须有 :keyTransitionGroup 靠它识别每一项(和 v-for 的规则一致)。
  • 想”移除后其余项平滑凑位”,给离开元素加 position: absolute(写进 leave-active 里),进阶时才需要。

速查卡片

场景 写法
单元素进退场 <Transition> + CSS 类(默认前缀 v-
自定义类名前缀 <Transition name="fade">.fade-enter-from
先出后进 mode="out-in"
列表增删 <TransitionGroup name="list" tag="ul">
列表项 必须有 :key
常用于 弹窗、Tab 切换、消息通知、折叠、列表增删
上一篇
Vue3 组合式函数
下一篇
Vue3 Teleport