Transition 与 TransitionGroup
列表、key 与过渡动画 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
Vue 内置的过渡系统让动画变成「加几个 class」的事。这一节掌握 <Transition> 的六个 class 钩子、<TransitionGroup> 处理列表增删移动的写法,以及动画与 v-if/v-show/动态组件的配合。
问题场景
删除一条待办时,列表「啪」地一下少一行,视觉上很突兀。你想加个淡出动画,用 CSS transition 直接写却不动——因为元素被 v-if 直接移除了,没有过渡的中间态。Vue 的 <Transition> 解决的正是「元素进入/离开时,先加 class 再等动画结束才移除」这套时序问题。
核心解释
<Transition> 包裹单个元素(或组件),在元素插入/移除时自动应用六个 class:
- 进入:
v-enter-from→v-enter-active→v-enter-to - 离开:
v-leave-from→v-leave-active→v-leave-to
<script setup>
import { ref } from "vue";
const show = ref(true);
</script>
<template>
<button @click="show = !show">切换</button>
<Transition name="fade">
<p v-if="show" class="box">我会淡入淡出</p>
</Transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
时序:元素插入时先加 fade-enter-from(初始态)和 fade-enter-active(过渡定义),下一帧移除 from 加 to,动画结束后清理所有 class。离开同理。name="fade" 决定 class 前缀,不写默认是 v-。
常用 CSS 动画(不是 transition)写法:用 @keyframes 配 *-enter-active { animation: ... }。<Transition> 会自动探测动画时长(transitionend/animationend),也可以用 :duration="300" 显式指定。
<TransitionGroup> 用于列表:它渲染一个真实容器(默认 <span>,通常指定 tag="ul"),支持 move 类处理排序移动:
<script setup>
import { ref } from "vue";
const todos = ref([
{ id: 1, title: "读 V07" },
{ id: 2, title: "写列表动画" },
]);
let seq = 3;
function add() {
todos.value.push({ id: seq, title: `新任务 ${seq++}` });
}
function remove(id) {
todos.value = todos.value.filter((t) => t.id !== id);
}
</script>
<template>
<button @click="add">添加</button>
<TransitionGroup name="list" tag="ul">
<li v-for="todo in todos" :key="todo.id">
{{ todo.title }}
<button @click="remove(todo.id)">删除</button>
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(20px);
}
/* 离开的元素脱离文档流,避免其他项跳动 */
.list-leave-active {
position: absolute;
}
.list-move {
transition: transform 0.3s ease;
}
</style>
三个关键点:tag="ul" 让 TransitionGroup 渲染成 ul(否则是 span 包着 li,语义错误);离开动画期间元素 position: absolute 才不会把后面的元素顶来顶去;.list-move 让重排有平滑位移——排序动画就是它实现的。
配合 v-show:<Transition> 也支持 v-show,但只触发 enter/leave 不触发 move。配合动态组件:<Transition mode="out-in"> 让旧组件先离场、新组件再入场,避免两个组件同时占位。
示例拆解
一个「通知条」组件,把进入/离开和状态切换串起来:
<script setup>
import { ref } from "vue";
const visible = ref(false);
const message = ref("");
function notify(text) {
message.value = text;
visible.value = true;
setTimeout(() => (visible.value = false), 2000);
}
</script>
<template>
<button @click="notify('已保存')">保存</button>
<Transition name="toast">
<div v-if="visible" class="toast" role="status">{{ message }}</div>
</Transition>
</template>
<style>
.toast {
position: fixed;
right: 16px;
bottom: 16px;
padding: 10px 16px;
background: #111;
color: #fff;
border-radius: 8px;
}
.toast-enter-active,
.toast-leave-active {
transition: all 0.25s ease;
}
.toast-enter-from,
.toast-leave-to {
opacity: 0;
transform: translateY(8px);
}
</style>
验证点:点保存,提示条淡入;2 秒后淡出。连续点两次,第二次会重置文案和计时——但如果两次间隔很短,visible 一直是 true,不会重新触发进入动画。需要「重复触发」时,给元素加 :key="message" 强制重建,或先置 false 再 nextTick 置 true。
动手任务
- 完成上面的通知条,加上「重复触发时重新播放进入动画」(提示:动态 key)。
- 把待办列表改成 TransitionGroup 版本,验证添加、删除、以及按标题排序时的移动动画。
- 用
<Transition mode="out-in">做一个「登录/注册」表单切换,观察旧表单先离场的效果。
常见错误
<Transition>里包多个根元素:过渡只支持单个元素/组件,多元素要用v-if/v-else或<TransitionGroup>。- 忘记写
-active类:只写from/to没有过渡定义,动画不生效。 - TransitionGroup 离开项不脱离文档流:删除时后面的项瞬间跳动,加
position: absolute到-leave-active。 v-show想用 move 动画:move 只属于 TransitionGroup 的排序场景,v-show 不触发。
检查与复盘
- 能默写六个过渡 class 的名字和时序
- 能写出列表增删 + 排序的完整 TransitionGroup 配置
- 能解释
-leave-active { position: absolute }解决什么问题
参考资料
- Vue 官方文档「Transition」:class 钩子、duration、JS 钩子
- Vue 官方文档「TransitionGroup」:move 动画与 FLIP 技术
读完这一课了?
标记完成后,工作台会安排接下来的学习。