Vue过渡动画从入门到装X:淡入淡出、滑动、列表动画、第三方库全搞定

一、Vue动画靠什么?两个组件打天下

Vue 帮你封装好了两个组件:

  • <Transition> :给单个元素或组件添加进入/离开动画。

  • <TransitionGroup> :给列表里的多个元素添加进入/离开动画,还能加移动动画。

你只需要把要做动画的元素包在里面,然后给几个特定名字的 CSS 类写好样式,Vue 就会在合适的时机自动添加/移除这些类,动画就出来了。

用人话讲: 你告诉 Vue "这个元素出来时要淡入,走时要淡出",Vue 就帮你管什么时候加什么类,你只负责写好类里面的 CSS 动画。


二、最简单的淡入淡出

先看一个最基础的例子:点击按钮,一段文字淡入淡出。

vue

复制代码
<template>
  <div>
    <button @click="show = !show">切换显示</button>

    <!-- 
      Transition 组件,name="fade" 就是给动画起个名字
      Vue 会根据这个名字去匹配 CSS 类
    -->
    <Transition name="fade">
      <!-- 只有这一个元素会被加上动画类 -->
      <p v-if="show">我会淡入淡出</p>
    </Transition>
  </div>
</template>

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

// 控制元素显示隐藏
const show = ref(false)
</script>

<style scoped>
/* 
   下面这几个类名是固定格式:name-enter-from、name-enter-active 等等
   因为 Transition 上的 name 是 "fade",所以类名都以 "fade" 开头
*/

/* 进入的初始状态:完全透明 */
.fade-enter-from {
  opacity: 0;
}

/* 进入的过程:过渡 0.5 秒,对 opacity 属性做动画 */
.fade-enter-active {
  transition: opacity 0.5s ease;
}

/* 进入的结束状态:完全不透明(其实这个是默认值,可以不写) */
.fade-enter-to {
  opacity: 1;
}

/* 离开的初始状态:完全不透明 */
.fade-leave-from {
  opacity: 1;
}

/* 离开的过程:过渡 0.5 秒 */
.fade-leave-active {
  transition: opacity 0.5s ease;
}

/* 离开的结束状态:完全透明 */
.fade-leave-to {
  opacity: 0;
}
</style>

代码拆解:

  • <Transition name="fade"> 里的 name 是这个动画的名字,随便起,但别和别的冲突。

  • CSS 类名格式:name-进入/离开的阶段

    • -enter-from:进入开始时的状态。

    • -enter-active:进入过程中的过渡效果(这里写 transition)。

    • -enter-to:进入结束时的状态。

    • -leave-from:离开开始时的状态。

    • -leave-active:离开过程中的过渡效果。

    • -leave-to:离开结束时的状态。

  • 我们只在 -enter-active-leave-active 里写了 transition,告诉 Vue "当状态变化时,用 0.5 秒平滑过渡"。

  • v-if="show" 切换时,Vue 会自动给这个 <p> 元素依次加上 fade-enter-fromfade-enter-activefade-enter-to 这几个类。


三、其实可以更简洁:利用默认状态省略一些类

很多时候 -enter-to-leave-from 就是元素本来的样子,不用专门写。上面的 CSS 可以精简为:

css

复制代码
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

解释:

  • 进入开始和离开结束都是透明(opacity: 0)。

  • 进入结束和离开开始都是不透明(默认),所以不用写。

  • 只要在 -active 里写好 transition,Vue 就会自动补齐中间状态。

这样以后你写动画,基本就记住这两个类就行。


四、给弹窗加个滑入滑出效果

淡入淡出太普通?咱们试试从上面滑下来,消失时滑上去。

vue

复制代码
<template>
  <div>
    <button @click="showModal = !showModal">切换弹窗</button>

    <!-- 这次 name 叫 "slide" -->
    <Transition name="slide">
      <div v-if="showModal" class="modal">
        <p>我是一个弹窗</p>
        <button @click="showModal = false">关闭</button>
      </div>
    </Transition>
  </div>
</template>

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

<style scoped>
.modal {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: white;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}

/* 进入的初始状态:向上偏移 30px,且透明 */
.slide-enter-from {
  opacity: 0;
  transform: translateY(-30px);
}

/* 离开的结束状态:同样向上偏移并透明 */
.slide-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

/* 进入和离开的过程:过渡 0.3 秒 */
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}
</style>

关键点:

  • transform: translateY(-30px) 让元素在 Y 轴上移 30px。

  • 配合 opacity,就形成了"从上方滑入并淡入"的效果。

  • 离开时反过来,滑上去并淡出。


五、自定义过渡类名:用第三方动画库 Animate.css

自己写 CSS 动画有时候挺麻烦,社区有很多现成的动画库,比如 Animate.css ,里面有几十种预设动画(弹入、翻转、抖动等)。Vue 的 <Transition> 允许你自定义过渡的类名,直接用 Animate.css 的类名。

第一步:index.html 里引入 Animate.css(或者 npm 安装导入)

html

复制代码
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />

第二步: 在组件里用

vue

复制代码
<template>
  <div>
    <button @click="show = !show">切换动画</button>

    <!-- 
      关键属性:
      enter-active-class  指定进入过程使用的类
      leave-active-class  指定离开过程使用的类
      这里直接写 Animate.css 的类名
    -->
    <Transition
      enter-active-class="animate__animated animate__bounceIn"
      leave-active-class="animate__animated animate__bounceOut"
    >
      <div v-if="show" class="box">
        我会弹入弹出
      </div>
    </Transition>
  </div>
</template>

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

<style scoped>
.box {
  width: 200px;
  height: 100px;
  background: #42b983;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}
</style>

解释:

  • enter-active-class 替代了之前的 name-enter-active,直接指定进入时用的 CSS 类。

  • Animate.css 的类名格式是 animate__animated + 具体动画名(如 animate__bounceIn)。

  • 这样你就能瞬间用上几十种预设动画,完全不用自己写关键帧。


六、给组件切换加过渡:mode 属性

有时候我们要在两个组件之间切换,比如登录页和注册页。默认情况下,一个组件离开和另一个组件进入是同时进行的,可能会有点视觉上的重叠。

<Transition> 有个 mode 属性,可以设置成:

  • out-in:当前元素先离开,完成后新元素再进入(推荐)。

  • in-out:新元素先进入,完成后旧元素再离开。

vue

复制代码
<template>
  <div>
    <button @click="isLogin = !isLogin">
      切换到 {{ isLogin ? '注册' : '登录' }}
    </button>

    <!-- mode="out-in" 保证旧组件完全离开后,新组件才进入 -->
    <Transition name="switch" mode="out-in">
      <LoginForm v-if="isLogin" key="login" />
      <RegisterForm v-else key="register" />
    </Transition>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import LoginForm from './LoginForm.vue'
import RegisterForm from './RegisterForm.vue'

const isLogin = ref(true)
</script>

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

重点:

  • 两个组件用 v-if / v-else 切换。

  • 别忘了加 key,不然 Vue 会复用组件,动画可能失效。

  • mode="out-in" 让切换更流畅。


七、列表动画:TransitionGroup

前面都是单个元素的进入/离开。列表呢?比如购物车删除一项,其他项往上移动时能不能也加个动画?用 <TransitionGroup>

vue

复制代码
<template>
  <div>
    <button @click="addItem">添加一项</button>
    <button @click="removeItem">删除最后一项</button>

    <!-- 
      TransitionGroup 组件,tag="ul" 表示渲染成 ul 标签
      name="list" 给动画起名
    -->
    <TransitionGroup name="list" tag="ul">
      <li v-for="item in items" :key="item.id" class="list-item">
        {{ item.text }}
        <button @click="removeSpecific(item.id)">删除</button>
      </li>
    </TransitionGroup>
  </div>
</template>

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

// 初始数据
const items = ref([
  { id: 1, text: '第一项' },
  { id: 2, text: '第二项' },
  { id: 3, text: '第三项' }
])

let nextId = 4

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

function removeItem() {
  items.value.pop()
}

function removeSpecific(id) {
  items.value = items.value.filter(item => item.id !== id)
}
</script>

<style scoped>
.list-item {
  padding: 10px;
  margin: 5px;
  background: #f9f9f9;
  border: 1px solid #ddd;
  border-radius: 4px;
  list-style: none;
}

/* 进入和离开的动画 */
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

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

/* 
   这个类很特殊:list-move
   当列表里的元素因为其他元素的位置变化而移动时,
   Vue 会给它加上这个类,让你能添加平滑移动动画 
*/
.list-move {
  transition: transform 0.5s ease;
}
</style>

解释:

  • <TransitionGroup> 会渲染成真实的标签(这里 tag="ul")。

  • 每个 v-for 出来的元素必须有唯一的 key

  • .list-move 这个类用来处理其他元素移动 时的过渡,让列表变化更平滑。比如删除一项,下面所有项会往上移,这个过程会加上 list-move 的过渡。


八、实战:带过渡动画的待办事项列表

我们综合一下,做一个完整的待办事项,包含添加、完成(删除)、全部清空,并且有滑动淡入淡出效果。

vue

复制代码
<template>
  <div class="todo-app">
    <h2>待办事项</h2>
    
    <!-- 输入框和添加按钮 -->
    <div class="input-group">
      <input
        v-model="newTodoText"
        @keyup.enter="addTodo"
        placeholder="输入待办内容,回车添加"
      />
      <button @click="addTodo">添加</button>
    </div>

    <!-- 待办列表,使用 TransitionGroup -->
    <TransitionGroup name="todo" tag="ul" class="todo-list">
      <li
        v-for="todo in todos"
        :key="todo.id"
        class="todo-item"
      >
        <span :class="{ done: todo.completed }">{{ todo.text }}</span>
        <div>
          <button @click="toggleComplete(todo.id)">
            {{ todo.completed ? '撤销' : '完成' }}
          </button>
          <button @click="removeTodo(todo.id)">删除</button>
        </div>
      </li>
    </TransitionGroup>

    <!-- 空状态提示 -->
    <p v-if="todos.length === 0" class="empty">暂无待办,添加一条吧</p>

    <!-- 底部操作 -->
    <div v-if="todos.length > 0" class="footer">
      <span>共 {{ todos.length }} 项</span>
      <button @click="clearCompleted">清除已完成</button>
    </div>
  </div>
</template>

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

const newTodoText = ref('')
const todos = ref([])
let nextId = 1

function addTodo() {
  const text = newTodoText.value.trim()
  if (!text) return  // 空内容不添加

  todos.value.push({
    id: nextId++,
    text,
    completed: false
  })
  newTodoText.value = ''  // 清空输入框
}

function removeTodo(id) {
  todos.value = todos.value.filter(todo => todo.id !== id)
}

function toggleComplete(id) {
  const todo = todos.value.find(todo => todo.id === id)
  if (todo) {
    todo.completed = !todo.completed
  }
}

function clearCompleted() {
  todos.value = todos.value.filter(todo => !todo.completed)
}
</script>

<style scoped>
.todo-app {
  max-width: 500px;
  margin: 0 auto;
}

.input-group {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.input-group input {
  flex: 1;
  padding: 6px 12px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  margin-bottom: 8px;
  background: #f5f5f5;
  border-radius: 4px;
}

.todo-item .done {
  text-decoration: line-through;
  color: #999;
}

.empty {
  text-align: center;
  color: #999;
}

.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid #eee;
}

/* 待办项的进入/离开动画 */
.todo-enter-from {
  opacity: 0;
  transform: translateX(-20px);
}

.todo-leave-to {
  opacity: 0;
  transform: translateX(20px);
}

.todo-enter-active,
.todo-leave-active {
  transition: all 0.4s ease;
}

/* 其他元素移动时的平滑过渡 */
.todo-move {
  transition: transform 0.4s ease;
}
</style>

效果:

  • 添加事项时,新项从左侧滑入并淡入。

  • 删除时,项向右侧滑出并淡出。

  • 完成的事项会加删除线,但不影响动画。

  • 下方元素上移时平滑过渡。


九、几个常见坑和技巧

1. 必须有 key

  • <Transition> 里用 v-if / v-else 切换多个元素时,每个元素要加 key,不然 Vue 会复用 DOM,动画可能不生效。

  • <TransitionGroup> 里,v-for 的每个项必须有唯一 key

2. 元素不要设置 display: none

  • 动画依赖元素的尺寸和位置,如果加了 display: none,Vue 可能获取不到初始状态,动画会失效。

3. 过渡模式用 out-in 更流畅

  • 组件切换时,mode="out-in" 能让上一个完全出去后,下一个再进来,避免重叠感。

4. 结合 JavaScript 钩子做复杂动画

  • 除了 CSS,<Transition> 还支持 @before-enter@enter@leave 等 JavaScript 钩子,你可以用 GSAP 等库做更复杂的动画。这里先不展开,知道有这个能力就行。

十、总结

今天我们学会了:

  • <Transition>:给单个元素/组件加进入离开动画。

  • <TransitionGroup>:给列表元素加动画,还能处理元素移动。

  • CSS 类命名规则name-enter-fromname-enter-activename-leave-to 等。

  • 自定义类名:配合 Animate.css 等库直接用现成动画。

  • mode 属性:控制多个元素切换时的顺序。

动画是用户体验的润滑剂,不用写太多,关键的地方加一点点,页面档次立马不一样。建议你把这些案例都敲一遍,尤其是待办事项那个,包含了大部分常用技巧,改成你自己的项目就能用。

有问题评论区说,我挨个回。下篇咱们聊 Vue 的组合式函数(Composables),把逻辑抽取得更优雅!

相关推荐
裕波1 小时前
Vue&ViteConf 2026 将于 7 月 18 日在上海举办,尤雨溪将现场发表主题演讲
vue.js·vite
IManiy1 小时前
总结之Vibe Coding前端骨架
前端
小和尚敲木头1 小时前
vue3 vite动态拼接图片路径
javascript
JS菌1 小时前
AI Agent 沙箱双层防护体系:从权限过滤到内核隔离的完整实现
前端·人工智能·后端
Aphasia3111 小时前
从输入URL到页面展示全流程
前端·面试
我叫黑大帅2 小时前
前端如何竖屏固定视口背景
前端·javascript·面试
abcy0712132 小时前
python pandas csv异步后台清洗前端优先返回成功信息
前端·python·pandas
不会敲代码12 小时前
我花了三天时间,终于把 Cookie、XSS、CSRF 和浏览器存储给整明白了
javascript·面试
IT_陈寒2 小时前
Vite这个坑我帮你踩了,动态导入居然这样才生效
前端·人工智能·后端