Vue Transition 動畫
Vue 提供了 <Transition> 和 <TransitionGroup> 元件,讓你可以輕鬆地為元素或元件的進入/離開添加過渡動畫效果。
Transition 基本用法
<Transition> 用於單一元素或元件的過渡:
<script setup>
import { ref } from 'vue'
const show = ref(true)
</script>
<template>
<button @click="show = !show">切換</button>
<Transition>
<p v-if="show">Hello Vue!</p>
</Transition>
</template>
<style>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
CSS 過渡 Class
Vue 會在適當的時機自動添加/移除這些 class:
進入過渡:
v-enter-from → v-enter-active → v-enter-to
離開過渡:
v-leave-from → v-leave-active → v-leave-to
| Class | 說明 |
|---|---|
v-enter-from | 進入的起始狀態 |
v-enter-active | 進入的過渡狀態(定義動畫) |
v-enter-to | 進入的結束狀態 |
v-leave-from | 離開的起始狀態 |
v-leave-active | 離開的過渡狀態(定義動畫) |
v-leave-to | 離開的結束狀態 |
命名過渡
使用 name 屬性來自訂 class 前綴:
<template>
<Transition name="fade">
<p v-if="show">Hello</p>
</Transition>
<Transition name="slide">
<p v-if="show">World</p>
</Transition>
</template>
<style>
/* fade 過渡 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* slide 過渡 */
.slide-enter-active,
.slide-leave-active {
transition: transform 0.5s ease;
}
.slide-enter-from {
transform: translateX(-100%);
}
.slide-leave-to {
transform: translateX(100%);
}
</style>
CSS 動畫
也可以使用 CSS Animation:
<template>
<Transition name="bounce">
<p v-if="show">彈跳效果</p>
</Transition>
</template>
<style>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
</style>
自訂過渡 Class
可以覆蓋預設的 class 名稱,適合搭配第三方動畫庫:
<template>
<!-- 使用 Animate.css -->
<Transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
>
<p v-if="show">Hello</p>
</Transition>
</template>
可用的屬性:
enter-from-classenter-active-classenter-to-classleave-from-classleave-active-classleave-to-class
過渡模式
當在兩個元素間切換時,可能會有新舊元素同時存在的問題。使用 mode 來控制:
<template>
<!-- 先離開,再進入 -->
<Transition name="fade" mode="out-in">
<component :is="currentComponent" />
</Transition>
<!-- 先進入,再離開 -->
<Transition name="fade" mode="in-out">
<component :is="currentComponent" />
</Transition>
</template>
使用 key 強制過渡
<template>
<Transition name="fade" mode="out-in">
<p :key="count">{{ count }}</p>
</Transition>
</template>
JavaScript Hooks
可以使用 JavaScript hooks 來控制過渡:
<template>
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<div v-if="show">Hello</div>
</Transition>
</template>
<script setup>
function onBeforeEnter(el) {
// 進入前
}
function onEnter(el, done) {
// 進入中
// 呼叫 done() 表示完成
done()
}
function onAfterEnter(el) {
// 進入後
}
function onEnterCancelled(el) {
// 進入被取消
}
function onBeforeLeave(el) {
// 離開前
}
function onLeave(el, done) {
// 離開中
done()
}
function onAfterLeave(el) {
// 離開後
}
function onLeaveCancelled(el) {
// 離開被取消(僅 v-show)
}
</script>
純 JavaScript 動畫
如果只使用 JavaScript 動畫,加上 :css="false" 來跳過 CSS 偵測:
<template>
<Transition
:css="false"
@enter="onEnter"
@leave="onLeave"
>
<div v-if="show">Hello</div>
</Transition>
</template>
<script setup>
function onEnter(el, done) {
// 使用 GSAP 或其他動畫庫
gsap.to(el, {
opacity: 1,
duration: 0.5,
onComplete: done
})
}
function onLeave(el, done) {
gsap.to(el, {
opacity: 0,
duration: 0.5,
onComplete: done
})
}
</script>
初始渲染的過渡
使用 appear 讓元素在初始渲染時也有過渡效果:
<template>
<Transition appear name="fade">
<p>頁面載入時會有過渡效果</p>
</Transition>
</template>
也可以自訂初始渲染的 class:
<template>
<Transition
appear
appear-from-class="custom-appear-from"
appear-active-class="custom-appear-active"
appear-to-class="custom-appear-to"
>
<p>Hello</p>
</Transition>
</template>
TransitionGroup
<TransitionGroup> 用於列表的過渡:
<script setup>
import { ref } from 'vue'
const items = ref([1, 2, 3, 4, 5])
let id = 6
function add() {
const index = Math.floor(Math.random() * items.value.length)
items.value.splice(index, 0, id++)
}
function remove() {
const index = Math.floor(Math.random() * items.value.length)
items.value.splice(index, 1)
}
</script>
<template>
<button @click="add">新增</button>
<button @click="remove">移除</button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
</template>
<style>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 移動過渡 */
.list-move {
transition: transform 0.5s ease;
}
/* 確保離開的元素脫離文件流 */
.list-leave-active {
position: absolute;
}
</style>
TransitionGroup 的差異
與 <Transition> 的差異:
- 預設會渲染一個元素,使用
tag指定 - 不支援
mode屬性 - 每個子元素必須有唯一的
key - 有額外的
v-moveclass 用於移動動畫
常見動畫效果
淡入淡出
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
滑動
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease;
}
.slide-enter-from {
transform: translateY(-20px);
}
.slide-leave-to {
transform: translateY(20px);
}
縮放
.scale-enter-active,
.scale-leave-active {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.scale-enter-from,
.scale-leave-to {
transform: scale(0.9);
opacity: 0;
}
翻轉
.flip-enter-active,
.flip-leave-active {
transition: transform 0.5s ease;
transform-style: preserve-3d;
}
.flip-enter-from {
transform: rotateY(-90deg);
}
.flip-leave-to {
transform: rotateY(90deg);
}
可重用的過渡元件
<!-- FadeTransition.vue -->
<template>
<Transition name="fade" mode="out-in">
<slot></slot>
</Transition>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
使用:
<template>
<FadeTransition>
<div v-if="show">Hello</div>
</FadeTransition>
</template>
搭配 Vue Router
<template>
<router-view v-slot="{ Component }">
<Transition name="fade" mode="out-in">
<component :is="Component" />
</Transition>
</router-view>
</template>
更多路由過渡請參考 Vue Router。
效能考量
- 使用
transform和opacity:這兩個屬性可以被 GPU 加速 - 避免
height或width動畫:這些屬性會觸發重排 - 使用
will-change:提示瀏覽器進行優化
.animated-element {
will-change: transform, opacity;
}