本文 AI 產出,尚未審核

Vue3 – Transition / 動畫效果

主題:動態 Transition Name


簡介

在單頁應用 (SPA) 中,使用 Vue3 內建的 <transition> 元件可以讓 UI 變化更自然、提升使用者體驗。大多數教學都會直接在 <transition> 上寫死 name="fade"name="slide" 等固定的動畫名稱,然而在實際開發中,我們常常需要根據不同的情境或使用者操作 動態切換 這些動畫名稱,例如在同一個列表裡根據項目的類型切換不同的進出動畫,或在路由切換時依據前後頁面的層級選擇不同的過渡效果。

本篇文章將深入探討 動態 transition name 的寫法與原理,從基本概念到實作範例,並說明常見的陷阱與最佳實踐,幫助初學者到中階開發者在 Vue3 中靈活運用動畫,打造更具互動性的介面。


核心概念

1. <transition> 基礎回顧

<transition> 會在其子元素的 enterleaveappear 階段自動添加 CSS class,配合 CSS transition 或 animation 完成動畫效果。預設的 class 命名規則如下(以 name="fade" 為例):

階段 加入的 class
enter-from fade-enter-from
enter-active fade-enter-active
enter-to fade-enter-to
leave-from fade-leave-from
leave-active fade-leave-active
leave-to fade-leave-to

只要在 CSS 中為這些 class 定義對應的樣式,即可完成動畫。

2. 為什麼需要「動態」的 transition name?

  • 多樣化 UI:同一個元件在不同情境下需要不同的過渡效果,例如彈出視窗的淡入淡出與側滑抽屜的滑入滑出。
  • 條件切換:根據使用者操作或資料狀態改變動畫,例如「新增」使用 slide-down,「刪除」使用 fade-out
  • 路由過渡:在 Vue Router 中,根據前後路由的層級或方向切換不同的過渡,提升導覽流暢度。

3. 動態設定 name 的方式

Vue3 允許將 <transition>name 屬性綁定到一個 響應式變數,只要變數改變,過渡效果會自動套用新的 class 名稱。常見寫法如下:

<transition :name="currentTransition">
  <div v-if="show">內容</div>
</transition>

setup() 中使用 refcomputed 來管理 currentTransition 即可。

4. 透過 v-bind 動態切換多個過渡

若需要同時控制 enterleave 的不同動畫名稱,可以使用 enter-active-classleave-active-class 等屬性,亦可綁定到變數:

<transition
  :enter-active-class="enterClass"
  :leave-active-class="leaveClass"
>
  <div v-if="show">內容</div>
</transition>

這種寫法在「切換模式」的 UI(例如編輯模式 ↔ 預覽模式)特別有用。

5. 結合 Vue Router 的 meta 來決定過渡

在路由設定中可於 meta 加入自訂的過渡名稱,於 <router-view> 外層使用 :key:name 動態取得:

// router/index.js
{
  path: '/home',
  component: Home,
  meta: { transition: 'slide-left' }
},
{
  path: '/about',
  component: About,
  meta: { transition: 'slide-right' }
}
<router-view v-slot="{ Component, route }">
  <transition :name="route.meta.transition || 'fade'">
    <component :is="Component" :key="route.fullPath" />
  </transition>
</router-view>

程式碼範例

下面提供 5 個實用範例,從最簡單的動態切換到結合 Router、組件間的協作,每段程式碼皆附上說明。


範例 1️⃣:最基本的動態 transition name

<!-- DynamicTransition.vue -->
<template>
  <div>
    <button @click="toggle">切換顯示</button>
    <select v-model="currentTransition">
      <option value="fade">淡入淡出</option>
      <option value="slide-down">向下滑入</option>
      <option value="zoom">放大縮小</option>
    </select>

    <transition :name="currentTransition">
      <p v-if="show" class="box">Hello Vue3!</p>
    </transition>
  </div>
</template>

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

const show = ref(false)
const currentTransition = ref('fade')

function toggle() {
  show.value = !show.value
}
</script>

<style scoped>
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity .4s ease; }

/* slide-down */
.slide-down-enter-from { transform: translateY(-20px); opacity: 0; }
.slide-down-enter-to { transform: translateY(0); opacity: 1; }
.slide-down-leave-from { transform: translateY(0); opacity: 1; }
.slide-down-leave-to { transform: translateY(-20px); opacity: 0; }
.slide-down-enter-active, .slide-down-leave-active { transition: all .3s ease; }

/* zoom */
.zoom-enter-from, .zoom-leave-to { transform: scale(0.5); opacity: 0; }
.zoom-enter-to, .zoom-leave-from { transform: scale(1); opacity: 1; }
.zoom-enter-active, .zoom-leave-active { transition: all .35s cubic-bezier(.4,0,.2,1); }
</style>

說明

  • currentTransitionref,綁定在 <transition>:name
  • 透過 <select> 即可即時切換不同的動畫類型,適合在 設定頁主題切換 時使用。

範例 2️⃣:依資料類型切換不同動畫

<!-- ListWithDynamicTransition.vue -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <transition :name="getTransition(item.type)">
        <span v-if="item.visible">{{ item.text }}</span>
      </transition>
    </li>
  </ul>

  <button @click="addItem('info')">新增 Info</button>
  <button @click="addItem('warning')">新增 Warning</button>
</template>

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

const items = reactive([
  { id: 1, text: '第一筆', type: 'info', visible: true },
  { id: 2, text: '第二筆', type: 'warning', visible: true }
])

let idCounter = 3

function addItem(type) {
  items.push({ id: idCounter++, text: `新項目 (${type})`, type, visible: true })
}

/* 根據項目類型回傳不同的 transition name */
function getTransition(type) {
  return type === 'warning' ? 'shake' : 'fade'
}
</script>

<style scoped>
/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }

/* shake(警告) */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}
.shake-enter-from { opacity: 0; }
.shake-enter-active {
  animation: shake .5s ease;
  opacity: 1;
}
.shake-leave-active { opacity: 0; transition: opacity .2s; }
</style>

說明

  • getTransitionitem.type 回傳不同的動畫名稱。
  • 這種寫法在 訊息列表通知中心 等需要根據層級呈現不同過渡的場景非常實用。

範例 3️⃣:使用 enter-active-class / leave-active-class 分別控制進出動畫

<!-- SeparateEnterLeave.vue -->
<template>
  <button @click="show = !show">Toggle Card</button>

  <transition
    :enter-active-class="enterClass"
    :leave-active-class="leaveClass"
  >
    <div v-if="show" class="card">卡片內容</div>
  </transition>

  <div class="controls">
    <label>Enter 動畫:
      <select v-model="enterClass">
        <option value="slide-in">Slide In</option>
        <option value="fade-in">Fade In</option>
      </select>
    </label>
    <label>Leave 動畫:
      <select v-model="leaveClass">
        <option value="slide-out">Slide Out</option>
        <option value="fade-out">Fade Out</option>
      </select>
    </label>
  </div>
</template>

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

const show = ref(false)
const enterClass = ref('slide-in')
const leaveClass = ref('slide-out')
</script>

<style scoped>
/* slide-in */
.slide-in-enter-from { transform: translateX(100%); }
.slide-in-enter-to { transform: translateX(0); }
.slide-in-enter-active { transition: transform .4s ease; }

/* slide-out */
.slide-out-leave-from { transform: translateX(0); }
.slide-out-leave-to { transform: translateX(100%); }
.slide-out-leave-active { transition: transform .3s ease; }

/* fade-in / fade-out */
.fade-in-enter-from, .fade-out-leave-to { opacity: 0; }
.fade-in-enter-to, .fade-out-leave-from { opacity: 1; }
.fade-in-enter-active, .fade-out-leave-active { transition: opacity .35s; }

.card {
  width: 200px;
  height: 120px;
  background: #f5f5f5;
  border: 1px solid #ddd;
  display: flex;
  align-items: center;
  justify-content: center;
}
.controls { margin-top: 1rem; }
</style>

說明

  • 透過 enter-active-classleave-active-class 可在同一個元件中 分別設定 進入與離開的動畫。
  • 非常適合 表單切換卡片翻轉 等需要不同進出效果的 UI。

範例 4️⃣:結合 Vue Router 的 meta.transition

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import Contact from '@/views/Contact.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { transition: 'slide-left' }
  },
  {
    path: '/about',
    name: 'About',
    component: About,
    meta: { transition: 'slide-right' }
  },
  {
    path: '/contact',
    name: 'Contact',
    component: Contact,
    meta: { transition: 'fade' }
  }
]

export default createRouter({
  history: createWebHistory(),
  routes
})
<!-- App.vue -->
<template>
  <nav>
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
    <router-link to="/contact">Contact</router-link>
  </nav>

  <router-view v-slot="{ Component, route }">
    <transition :name="route.meta.transition || 'fade'">
      <component :is="Component" :key="route.fullPath" />
    </transition>
  </router-view>
</template>

<script setup>
/* 無需額外程式碼,主要靠 router meta */
</script>

<style>
/* slide-left */
.slide-left-enter-from { transform: translateX(100%); }
.slide-left-enter-to { transform: translateX(0); }
.slide-left-enter-active,
.slide-left-leave-active { transition: transform .4s ease; }
.slide-left-leave-to { transform: translateX(-100%); }

/* slide-right */
.slide-right-enter-from { transform: translateX(-100%); }
.slide-right-enter-to { transform: translateX(0); }
.slide-right-enter-active,
.slide-right-leave-active { transition: transform .4s ease; }
.slide-right-leave-to { transform: translateX(100%); }

/* fade */
.fade-enter-from, .fade-leave-to { opacity: 0; }
.fade-enter-to, .fade-leave-from { opacity: 1; }
.fade-enter-active, .fade-leave-active { transition: opacity .3s; }
</style>

說明

  • 每條路由在 meta 中定義自己的 transition 名稱。
  • <router-view> 使用 具名插槽 (v-slot) 取得當前路由資訊,動態套用對應動畫。
  • 這種寫法在 SPA 大型專案 中,讓不同頁面切換時保持一致且可維護的過渡規則。

範例 5️⃣:在同一個組件內切換「進入」與「離開」動畫的混合使用

<!-- ModalWithDynamicTransition.vue -->
<template>
  <button @click="open = true">開啟 Modal</button>

  <transition
    :name="modalTransition"
    @before-enter="setEnterDirection"
    @before-leave="setLeaveDirection"
  >
    <div v-if="open" class="modal" @click.self="open = false">
      <h3>動態過渡 Modal</h3>
      <p>點擊背景關閉,或切換方向。</p>
      <div class="controls">
        <button @click="direction = 'left'">左側滑入</button>
        <button @click="direction = 'right'">右側滑入</button>
        <button @click="direction = 'top'">上方淡入</button>
      </div>
    </div>
  </transition>
</template>

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

const open = ref(false)
const direction = ref('left') // left / right / top

/* 根據 direction 動態產生 transition name */
const modalTransition = computed(() => {
  switch (direction.value) {
    case 'right': return 'slide-right'
    case 'top': return 'fade-top'
    default: return 'slide-left'
  }
})

function setEnterDirection(el) {
  // 可在此處根據 direction 加入自訂屬性,供 CSS 使用
  el.dataset.dir = direction.value
}

function setLeaveDirection(el) {
  // leave 時同樣保留方向資訊
  el.dataset.dir = direction.value
}
</script>

<style scoped>
.modal {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.5);
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal > div {
  background: #fff;
  padding: 2rem;
  border-radius: 8px;
}

/* slide-left */
.slide-left-enter-from { transform: translateX(-100%); }
.slide-left-enter-to { transform: translateX(0); }
.slide-left-enter-active,
.slide-left-leave-active { transition: transform .35s ease; }
.slide-left-leave-to { transform: translateX(-100%); }

/* slide-right */
.slide-right-enter-from { transform: translateX(100%); }
.slide-right-enter-to { transform: translateX(0); }
.slide-right-enter-active,
.slide-right-leave-active { transition: transform .35s ease; }
.slide-right-leave-to { transform: translateX(100%); }

/* fade-top */
.fade-top-enter-from { opacity: 0; transform: translateY(-30px); }
.fade-top-enter-to { opacity: 1; transform: translateY(0); }
.fade-top-enter-active,
.fade-top-leave-active { transition: all .3s ease; }
.fade-top-leave-to { opacity: 0; transform: translateY(-30px); }
</style>

說明

  • 透過 computed 動態產生 modalTransition,讓使用者在 UI 上切換不同的彈出方向。
  • @before-enter / @before-leave 事件可在進入/離開前寫入自訂屬性,讓 CSS 取得額外資訊。
  • 此模式適合 彈窗、抽屜、提示訊息 等需要根據使用情境改變動畫的元件。

常見陷阱與最佳實踐

陷阱 可能的結果 解決方案 / 最佳實踐
name 綁定到非響應式變數 動畫名稱不會更新,畫面仍使用舊的過渡 確保 name 來源是 refreactivecomputed
過渡類別與 CSS 重名 產生意外的樣式衝突,動畫失效 建議在專案中使用 命名空間(如 my-fade-enter)或 scoped CSS
同時使用 nameenter-active-class 兩者會相互覆寫,造成不預期的階段樣式 若要自訂進出階段,盡量只使用 enter-active-class / leave-active-class,或在 name 基礎上再加上自訂類別
<transition> 內放置多個根元素 Vue 只會對第一個子元素套用過渡,導致部分元素無動畫 使用 <template> 包裹 多個元素,或改用 <transition-group>
CSS 動畫與過渡同時使用 兩者的時間衝突,動畫卡頓 明確分離 transitionanimation,或使用 animationend 事件確保結束後再切換狀態
路由過渡忘記加 key 同一個組件切換路由時不會重新觸發過渡 <router-view> 中使用 :key="route.fullPath",保證每次切換都重新掛載元件

最佳實踐

  1. 保持過渡名稱語意化:如 slide-leftfade-up,讓其他開發者一眼就能懂。
  2. 將動畫 CSS 抽離成可重用的檔案(如 transitions.css),配合 BEMCSS Modules,提升維護性。
  3. 使用 prefers-reduced-motion 媒體查詢,對需要減少動畫的使用者提供無動畫或簡化版過渡。
@media (prefers-reduced-motion: reduce) {
  .fade-enter-active,
  .fade-leave-active,
  .slide-left-enter-active,
  .slide-left-leave-active { transition: none; }
}

實際應用場景

場景 為何需要動態 transition name 典型實作方式
電商商品列表 不同類別商品(新品、特價、熱賣)需要不同的進場動畫,提升視覺層次感 依商品 type 回傳 fadeslide-upbounce
表單分頁 Wizard 前後頁面切換方向不同(左滑/右滑),且在驗證失敗時需要「抖動」提示 使用 enter-active-class / leave-active-class 分別設定 slide-leftshake
多語系切換 切換語系時希望使用淡入淡出或滑動過渡,根據使用者偏好切換 透過全局狀態 i18nTransition 控制 <transition :name="i18nTransition">
手機端抽屜選單 抽屜左滑出、右滑入,且在不同螢幕尺寸下可能改為淡入 resize 事件中更新 drawerTransition
SPA 大型平台 各子系統(Dashboard、Report、Setting)路由切換需不同過渡,提升導覽感知 在路由 meta.transition 中統一管理,配合 router-view 動態套用

總結

  • 動態 transition name 為 Vue3 提供了彈性極高的動畫控制方式,讓開發者能根據 資料、使用者行為或路由資訊 變化即時切換過渡效果。
  • 只要把 nameenter-active-classleave-active-class 綁定到 響應式變數,配合 computedrouter meta,就能在不改寫大量程式碼的情況下,為不同情境提供專屬動畫。
  • 在實作時要注意 變數的響應性、CSS 命名衝突、以及 過渡與動畫的時間協調,並遵循 可存取性prefers-reduced-motion)的最佳實踐。

掌握了這套「動態切換過渡」的技巧後,你的 Vue3 應用將能更靈活地呈現視覺效果,提升使用者體驗,同時保持程式碼的可維護性與可擴充性。祝開發順利,玩得開心!