Appearance
用 Transition 做進出場動畫
到目前為止,v-if 的顯示隱藏、:is 的元件切換,都是瞬間完成的「硬切」。<Transition> 讓元素的進場與退場可以套用 CSS 動畫,而且不用改任何邏輯,只要包一層。
<Transition> 基本用法
把會進出畫面的單一元素(或元件)包進 <Transition>,並給它一個 name:
src/App.vue:
vue
<script setup>
import { ref } from 'vue'
const added = ref(false)
</script>
<template>
<main class="app">
<button type="button" @click="added = !added">
{{ added ? '從購物車移除' : '加入購物車' }}
</button>
<Transition name="fade">
<p v-if="added" class="notice">已加入購物車!</p>
</Transition>
</main>
</template>
<style scoped>
.app {
padding: 2rem;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>按下按鈕,提示文字淡入;再按一次,淡出後才從 DOM 移除。邏輯完全沒變,還是那個 v-if;<Transition> 做的事,是在進出場的過程中幫元素掛上約定好的 CSS class,動畫本身由我們的 CSS 決定。
CSS class 的約定
<Transition name="fade"> 在進出場過程會依序掛上六個 class,名稱以 name 為前綴:
| Class | 掛上的時機 |
|---|---|
fade-enter-from | 進場的起點(插入前的樣子),下一影格移除 |
fade-enter-active | 整個進場過程,transition 屬性通常寫這裡 |
fade-enter-to | 進場的終點(完成後的樣子) |
fade-leave-from | 退場的起點 |
fade-leave-active | 整個退場過程 |
fade-leave-to | 退場的終點,動畫結束後元素才移除 |
所以剛才的範例翻譯過來就是:進場從 opacity: 0(fade-enter-from)過渡到正常;退場從正常過渡到 opacity: 0(fade-leave-to);過渡花 0.3 秒(兩個 -active)。Vue 負責在對的時機掛 class 與延後移除元素,動畫的長相全部寫在 CSS,這就是 <Transition> 的契約。
它也能包住上一節學過的東西:<Transition> 裡放 <component :is>,元件切換就有進出場;搭配 mode="out-in"(舊的先退場、新的再進場)可以避免兩個元件短暫並存的跳動。要注意 <Transition> 只接受單一子元素或元件。
<TransitionGroup>:列表的進出場
列表的新增與移除,用 <TransitionGroup>。它是 <Transition> 的列表版:允許多個子元素,每個子元素都套用同一組 class 約定:
vue
<script setup>
import { ref } from 'vue'
const books = ref([
{ id: 1, title: '重新認識 Vue.js' },
{ id: 2, title: '008 天絕對看不完' }
])
let nextBookId = 3
function addBook() {
books.value.push({
id: nextBookId,
title: `新書 ${nextBookId}`
})
nextBookId++
}
function removeBook(id) {
books.value = books.value.filter(book => book.id !== id)
}
</script>
<template>
<main class="app">
<button type="button" @click="addBook">
新增書籍
</button>
<TransitionGroup name="fade" tag="ul" class="book-list">
<li v-for="book in books" :key="book.id" class="book-item">
<span>{{ book.title }}</span>
<button type="button" @click="removeBook(book.id)">
移除
</button>
</li>
</TransitionGroup>
</main>
</template>
<style scoped>
.app {
display: grid;
gap: 1rem;
padding: 2rem;
}
.book-list {
display: grid;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
}
.book-item {
display: flex;
align-items: center;
gap: 0.75rem;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>- 重新認識 Vue.js
- 008 天絕對看不完
tag="ul" 指定它渲染成 <ul>。這裡有一個比平常更嚴格的要求:每個子元素都必須有穩定且唯一的 key。第二章講過 key 是 Vue 追蹤列表項目的身分證;對 <TransitionGroup> 來說,沒有正確的 key,Vue 就分不清「哪一筆是新增、哪一筆是移除」,動畫會套在錯的項目上。用陣列 index 當 key 在這裡幾乎保證出錯。
本例只處理新增與移除的淡入淡出,沒有設定 fade-move。因此移除動畫結束後,其他項目會直接跳到空出的位置,這是預期行為,不是動畫失效。<TransitionGroup> 也能替「移動中」的項目掛 fade-move class,讓補位或排序有移動動畫;這屬於進階用法,需要時再查官方文件即可。
進出場動畫補完,本章的元件組合工具就全部到齊了。最後一節換一個角度收尾:把這些工具交給 AI 組裝,看看我們該在哪些地方把關。
