在魔法學院裡,有個小秘密:
真正的高階魔法師,不只是讓咒語發動,更要讓過程優雅。
你是否曾經打開一個網站,點下按鈕後畫面突然「啪!」地一閃?
那種突兀的切換,就像傳送門在你腳邊炸開一樣,讓人心頭一震。
雖然功能正確,但那份「突兀感」會讓使用者的體驗失去魔力。
而如果能在切換時淡入淡出、滑入滑出,就能讓畫面像吟唱的魔法詩篇——
平滑、自然、流動。
今天,我們將學習 Vue 的過場魔法(Transition Magic),
用 <Transition>
與 <TransitionGroup>
讓你的飲料系統「動起來」,
讓點單的每一步都充滿柔和的魔法氣息
其實我們今天要學的東西就是轉場動畫
不過我們不會講得特別複雜
會把顯示的畫面淡淡的顯示淡入這樣
我們身為要求很高的user當然會把要求看到網頁不會卡卡的~
或是突然蹦出來這樣的突兀~!!
需求 | 使用者 | 目的 | 功能點 |
---|---|---|---|
切換頁面不突兀 | 一般使用者 | 保持情緒穩定與方向感 | router-view 外層 <Transition> |
填表更有節奏 | 點單者 | 逐步完成、不被資訊淹沒 | 表單步驟 <Transition> |
列表更好讀 | 祕書/收單者 | 新增/刪除時不跳動 | <TransitionGroup> + FLIP |
等待不焦慮 | 所有人 | 讀取時給「還在處理」的回饋 | Loading + 減少布局抖動 |
尊重可近性 | 對動效敏感的使用者 | 避免眩暈/突兀動畫 | prefers-reduced-motion 查詢 |
我們可以看一下時序突來呈現整個程式運作的時間互動
<Transition>
:單一元素/區塊的出入場魔法(常用於 v-if
、router-view
)。<TransitionGroup>
:多元素的插入/移除/排序動畫(自帶 FLIP 位移魔法)。*-enter-from → *-enter-active → *-enter-to
(離場則反向)。@before-enter
、@enter
、@after-leave
。<RouterView>
外層加 <Transition>
,就能實現頁面級轉場。prefers-reduced-motion
:系統層咒語,尊重對動畫敏感的使用者。今天我們不會動到後端的code~
以下程式碼僅為「施展過場魔法」,不會影響邏輯功能。
讓畫面「淡入、滑入、自然地轉換」。
<template>
<main class="page">
<h1>飲料點單系統</h1>
<nav style="display:flex; gap:8px; margin:12px 0;">
<router-link to="/order" class="btn">點餐之塔</router-link>
<router-link to="/summary" class="btn">結算之室</router-link>
</nav>
<RouterView v-slot="{ Component }">
<Transition name="page" mode="out-in">
<component :is="Component" />
</Transition>
</RouterView>
</main>
</template>
<style>
.page-enter-from { opacity: 0; transform: translateY(6px) scale(0.98); }
.page-enter-active, .page-leave-active { transition: all .18s ease; }
.page-leave-to { opacity: 0; transform: translateY(-6px) scale(0.98); }
@media (prefers-reduced-motion: reduce) {
.page-enter-active, .page-leave-active { transition-duration: 0s; }
}
</style>
語法 | 用途 | 魔法描述 |
---|---|---|
.page-enter-from |
進場初始狀態 | 畫面剛出現時透明且略低,像魔法陣正在升起。 |
.page-enter-active |
動畫過程 | 控制動畫時間與速率。 |
.page-leave-to |
離場結束狀態 | 畫面漸隱,往上漂浮消散。 |
transition: all .18s ease; |
動畫時間 | 0.18 秒的柔和節奏。 |
@media (prefers-reduced-motion) |
系統檢測 | 為動效敏感者關閉動畫。 |
<template>
<OptionGroup label="步驟 1:選擇飲料" :options="drinks" v-model="drink" required />
<Transition name="step" appear>
<div v-if="drink">
<OptionGroup label="步驟 2:選擇甜度" :options="optSweetness" v-model="sweetness" required />
</div>
</Transition>
<Transition name="step">
<div v-if="drink && sweetness">
<OptionGroup label="步驟 3:選擇冰量" :options="optIce" v-model="ice" required />
</div>
</Transition>
<button :disabled="!canSubmit" class="submit" @click="addOrder">
{{ canSubmit ? '送出' : '請完成所有必填' }}
</button>
</template>
<style scoped>
.step-enter-from { opacity: 0; transform: translateY(6px); }
.step-enter-active, .step-leave-active { transition: all .16s ease; }
.step-leave-to { opacity: 0; transform: translateY(-6px); }
@media (prefers-reduced-motion: reduce) {
.step-enter-active, .step-leave-active { transition-duration: 0s; }
}
</style>
語法 | 用途 | 魔法描述 |
---|---|---|
.step-enter-from |
進場起點 | 選項淡淡浮現,像召喚出的選單。 |
.step-enter-active |
控制動畫過程 | 0.16 秒內完成柔順滑入。 |
.step-leave-to |
消失階段 | 淡出並上升,如能量散去。 |
translateY() |
位移效果 | 製造上下滑動感。 |
opacity |
不透明度 | 讓過場自然柔和。 |
<template>
<TransitionGroup name="list" tag="ul">
<li v-for="(o, i) in orders" :key="o.id || i" class="order">
<div class="row">
<div class="col">
<span class="idx">{{ i + 1 }}.</span>
<span class="name">{{ o.name }}</span>
<span class="pill">{{ o.drink }}</span>
<span class="pill" :class="o.ice === '去冰' ? 'is-noice' : 'is-ice'">{{ o.ice }}</span>
<span class="pill" :class="o.sweetness === '去糖' ? 'is-nosugar' : 'is-sugar'">{{ o.sweetness }}</span>
<span v-if="o.note" class="note">備註:{{ o.note }}</span>
</div>
<div class="actions">
<router-link v-if="o.id" class="btn btn-sm" :to="`/order/${o.id}`">詳情</router-link>
<button class="btn btn-sm" @click="$emit('edit', { index: i, patch: o })">編輯</button>
<button class="btn btn-sm del" @click="$emit('remove', i)">刪除</button>
</div>
</div>
</li>
</TransitionGroup>
</template>
<style scoped>
.list-enter-from, .list-leave-to { opacity: 0; transform: translateY(6px); }
.list-enter-active, .list-leave-active { transition: all .14s ease; }
.list-move { transition: transform .14s ease; }
@media (prefers-reduced-motion: reduce) {
.list-enter-active, .list-leave-active, .list-move { transition-duration: 0s; }
}
</style>
語法 | 用途 | 魔法描述 |
---|---|---|
.list-enter-from |
新項目初始 | 元素剛生成時半透明、微下沉。 |
.list-enter-active |
插入動畫 | 平滑浮上、淡入。 |
.list-leave-to |
離場動畫 | 項目往下淡出,像被捲回卷軸。 |
.list-move |
項目排序動畫 | 使用 FLIP 技術自動調整位置。 |
.loading-message {
opacity: 0;
animation: fadeIn .18s ease forwards;
}
@keyframes fadeIn { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
.loading-message { animation: none; opacity: 1; }
}
語法 | 用途 | 魔法描述 |
---|---|---|
@keyframes fadeIn |
定義動畫幀 | 從透明到顯現。 |
animation: fadeIn .18s ease forwards; |
播放動畫 | Loading 從無到有的漸入咒語。 |
forwards |
保留終態 | 動畫完成後保持不透明。 |
下面是如果使用css動畫的一些提示: 可以參考一下
key 必穩定:TransitionGroup
請使用固定 id
,避免動畫錯位。
避免雙重過場:同一元素上不要套兩層動畫,容易衝突。
尊重可近性:支援 prefers-reduced-motion
,讓每位使用者都能舒服體驗。
短而細的動畫:150–200ms 為最佳節奏,輕盈不拖泥帶水。
避免畫面跳動:預留空間避免 Layout Shift。
僅在可見時啟動:條件渲染節省效能。
類別 | 適用範圍 | 主要屬性 | 動效時間 | 魔法效果 | 備註 |
---|---|---|---|---|---|
.page-* |
整頁切換 | opacity , transform |
0.18s | 頁面淡入淡出 + 浮動 | 用於 <RouterView> |
.step-* |
表單步驟 | opacity , translateY |
0.16s | 表單項目滑入滑出 | 用於 <Transition> |
.list-* |
訂單列表 | opacity , transform |
0.14s | 插入/刪除平滑位移 | 用於 <TransitionGroup> |
.list-move |
列表重排 | transform |
0.14s | FLIP 技術自動補位 | Vue 自動套用 |
.loading-message |
載入提示 | opacity , animation |
0.18s | 淡入 Loading | 支援 reduced motion |
今天我們的程式碼
day18 github
今天,我們讓「飲料點單系統」不再只是能運作的程式,
而是一場「流動的魔法表演」。
<Transition>
,畫面淡入淡出如施法。<TransitionGroup>
讓項目增減自然、不跳動。魔法師心得:
過場不是特效,而是「使用者與系統之間的節奏對話」。
一點流暢的過場,能讓操作更直覺、體驗更優雅。
其實前端工程師就是要學這麼多東西~
除了資料以外還有視覺效果處理的方式
另外能讓使用者體感更好的介面也是一大課題
接者我會把我想寫的或傳達的跟大家分享~
請大家繼續跟者大法師柯基完成最後的旅途吧~!!