本文 AI 產出,尚未審核

Vue3 - 路由系統(Vue Router 4)

主題:路由 Transition 動畫


簡介

在單頁應用(SPA)中,路由切換的過程往往是使用者感受到的第一個交互體驗。若切換瞬間毫無過渡,畫面會顯得突兀,使用者甚至會誤以為程式卡住。相反地,加入適當的 transition 動畫 不僅能緩解這種突兀感,還能提升整體 UI 的流暢度與專業度。

Vue3 搭配 Vue Router 4,提供了極為簡潔的方式在路由切換時套用動畫。只要把 <router-view> 包在 <transition>(或 <transition-group>)裡,配合 CSS 或 JavaScript 的過渡效果,即可輕鬆實作 頁面切換動畫。本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領你在 Vue3 專案中加入路由動畫。


核心概念

1. <router-view><transition> 的基本結合

<router-view> 是 Vue Router 用來渲染當前路由對應組件的佔位符。將它包在 <transition> 中,Vue 會在路由組件 進入(enter)離開(leave) 時自動套用過渡類別。

<!-- App.vue -->
<template>
  <div id="app">
    <!-- 1. 包在 transition 中 -->
    <transition name="fade" mode="out-in">
      <router-view />
    </transition>
  </div>
</template>

<style>
/* 2. CSS transition 定義 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.4s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
  • name="fade":Vue 會自動產生 fade-enter-activefade-enter-fromfade-enter-tofade-leave-activefade-leave-fromfade-leave-to 這六個類別。
  • mode="out-in":先離開舊頁面,完成後再進入新頁面,避免兩個頁面同時呈現造成閃爍。

2. 多種動畫類型:CSS vs JavaScript

2.1 只用 CSS(最簡單)

如上例,只需要寫 CSS 即可,適合 淡入淡出滑動縮放 等簡單效果。

2.2 使用 JavaScript Hook(更彈性)

當動畫需求超出 CSS 能力(例如需要根據路由參數動態計算動畫時間),可以利用 <transition>JavaScript 生命週期鉤子

<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <router-view />
  </transition>
</template>

<script setup>
function beforeEnter(el) {
  // 初始化樣式
  el.style.transform = 'translateX(100%)';
}
function enter(el, done) {
  // 使用 requestAnimationFrame 觸發瀏覽器重排
  requestAnimationFrame(() => {
    el.style.transition = 'transform 0.5s ease-out';
    el.style.transform = 'translateX(0)';
  });
  // 動畫結束時呼叫 done
  el.addEventListener('transitionend', done);
}
function leave(el, done) {
  el.style.transition = 'transform 0.4s ease-in';
  el.style.transform = 'translateX(-100%)';
  el.addEventListener('transitionend', done);
}
</script>
  • done 必須在動畫結束時呼叫,否則 Vue 會一直等待,導致畫面卡住。
  • 透過 el.style 可以根據 路由 meta螢幕尺寸 動態調整動畫參數。

3. 為每個路由設定不同的動畫

有時候不同頁面需要不同的過渡效果(例如列表頁滑入、詳細頁淡入)。可以把動畫名稱寫在 路由 meta,再在 <transition> 中動態取得。

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

const routes = [
  { path: '/', component: Home, meta: { transition: 'fade' } },
  { path: '/about', component: About, meta: { transition: 'slide-left' } },
  { path: '/profile/:id', component: Profile, meta: { transition: 'slide-up' } },
];

export default createRouter({
  history: createWebHistory(),
  routes,
});
<!-- App.vue -->
<template>
  <transition :name="transitionName" mode="out-in">
    <router-view v-slot="{ Component, route }">
      <!-- 讓 Vue 把 key 綁在路由路徑上,確保每次切換都有重新渲染 -->
      <component :is="Component" :key="route.fullPath" />
    </router-view>
  </transition>
</template>

<script setup>
import { useRoute } from 'vue-router';
import { computed } from 'vue';

const route = useRoute();
const transitionName = computed(() => route.meta.transition || 'fade');
</script>

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

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

/* slide-up */
.slide-up-enter-active,
.slide-up-leave-active { transition: transform .4s; }
.slide-up-enter-from { transform: translateY(100%); }
.slide-up-leave-to   { transform: translateY(-100%); }
</style>
  • v-slot="{ Component, route }" 讓我們可以直接取得 route 物件,避免在 setup() 中額外 watch
  • :key="route.fullPath" 確保每次路由變動時 <component> 都會重新掛載,觸發過渡。

4. 嵌套路由的動畫

在「主畫面」與「子畫面」同時需要動畫時,分層使用 <transition> 能達到更細緻的控制。

<!-- Dashboard.vue (父層) -->
<template>
  <div class="dashboard">
    <transition name="slide-left" mode="out-in">
      <router-view />
    </transition>
  </div>
</template>
<!-- DashboardHome.vue (子層) -->
<template>
  <transition name="fade" mode="out-in">
    <router-view />
  </transition>
</template>
  • 父層的 <transition> 控制 子路由切換(例如左側選單切換),子層再自行加入淡入淡出,形成 多層次動畫

5. 使用 transition-group 處理列表型路由

如果路由切換時同時要呈現多筆資料(例如搜尋結果列表),<transition-group> 可以讓每筆項目分別動畫。

<template>
  <transition-group name="list" tag="ul">
    <li v-for="item in items" :key="item.id" class="list-item">
      {{ item.title }}
    </li>
  </transition-group>
</template>

<style>
.list-enter-active,
.list-leave-active { transition: all .3s ease; }
.list-enter-from { opacity: 0; transform: translateY(20px); }
.list-leave-to   { opacity: 0; transform: translateY(-20px); }
</style>
  • 這裡的 items 可能是根據路由參數(例如搜尋關鍵字)而改變的資料陣列。
  • 每筆項目會依序淡入或淡出,提升使用者的視覺追蹤感。

常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記加 key <router-view> 沒有 key,Vue 會重用同一個組件實例,導致過渡不觸發。 使用 :key="route.fullPath":key="$route.path"
同時使用 out-inmode="in-out" 設定衝突會使舊頁面仍在渲染,產生閃爍。 只保留一種 mode,根據需求選擇 out-in(先離開)或 in-out(先進入)。
CSS 動畫時間與 JavaScript done 不一致 transition 時間長於 done 呼叫時機,動畫會被中斷。 在 JavaScript 鉤子裡使用 el.addEventListener('transitionend', done),或手動 setTimeout(done, duration)
過度使用複雜動畫 造成頁面渲染卡頓,特別在低階裝置。 只在關鍵切換使用較簡單的動畫,保持 duration ≤ 400ms
路由 Meta 未同步 動畫名稱寫在 meta 卻忘了在 App.vue 讀取,導致全部使用預設動畫。 確保 `transitionName = computed(() => route.meta.transition

最佳實踐

  1. 保持動畫簡潔:淡入淡出、滑動是最常見且效能最佳的選擇。
  2. 統一管理動畫時間:在專案根目錄建立 variables.css,統一 --transition-duration,避免不同頁面時間不一致。
  3. 使用 mode="out-in":大多數情況下,先離開再進入能避免內容重疊。
  4. 針對手機端調整:可在 @media 中降低 duration,提升流暢度。
  5. 測試不同瀏覽器:Safari、Edge 在 transformopacity 的硬體加速表現略有差異,務必在多平台測試。

實際應用場景

  1. 電商商品列表 → 商品詳情

    • 列表使用 slide-left,點擊商品後切換到 fade,讓使用者感受到「從列表進入」的層次感。
  2. 表單步驟導覽(Wizard)

    • 每一步路由使用 slide-up,切換時同時使用 transition-group 動畫顯示表單欄位的出現/消失。
  3. 管理後台側邊選單

    • 左側選單點擊切換主內容時使用 out-in 搭配 slide-left,讓畫面感覺像是「抽屜」打開。
  4. 搜尋結果即時刷新

    • 依據路由參數(/search?q=xxx)更新 items,使用 transition-group 讓每筆結果淡入,提升使用者對結果變化的感知。
  5. 多語系切換

    • 切換語系路由時使用 fade,同時在 meta 設定 transition: 'fade',避免因文字長度差異產生突兀感。

總結

Vue Router 4 為 Vue3 提供了 簡潔且彈性的路由過渡機制。只要把 <router-view> 包在 <transition>(或 <transition-group>)裡,配合 CSSJavaScript 鉤子,就能在路由切換時呈現流暢的動畫效果。透過 路由 meta 動態指定動畫名稱、使用 key 確保每次重新渲染、以及在 嵌套路由 中分層運用 <transition>,可滿足從簡單淡入淡出到複雜多層次動畫的各種需求。

在實務開發中,保持動畫簡潔、統一時間、避免過度渲染 是提升使用者體驗的關鍵。掌握本文的核心概念與範例後,你就能在 Vue3 專案中自如地加入路由動畫,為使用者帶來更具沉浸感的互動體驗。祝開發順利,玩得開心!