前言
transition
是 Vue 提供的一個 built-in component,可以很快速且便利的為動態的元件加上轉場效果,本篇主要會分享 transition 的基本用法和巢狀用法
*註:以下會用 Vue3 的寫法,但大致上與 Vue2 相同,不同之處會附上 Vue2 的用法
transition usage
transition 的使用需要搭配 v-if
、v-show
或動態 <component>
,其實就是在元件消失或出現時,Vue 會做一些處理,加上特定的 class 來實現轉場動畫。使用上也很簡單,只要在需要動畫的元素外層加上 <Transition>
,再寫轉場的 css 即可
<button @click="show = !show">toggle</button>
<Transition>
<div v-if="show" class="box">something here</div>
</Transition>
.v-enter-active, .v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from, .v-leave-to {
opacity: 0;
}
class 的用法可以參考官網圖,寫得很清楚,大部分時候我們只需要特別定義最初 .v-enter-from
跟最後 .v-leave-to
這兩個狀態的樣式就好
v-enter-from
在 Vue2 的 class name 是v-enter
當專案內有多個不同效果的 transition 時,可以用 name
定義,class 原本的 v-
前綴也會變成對應的名稱
<Transition name="fade">
<div v-if="show" class="box">something here</div>
</Transition>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
如果希望在 DOM 初始化的時候也可以先跑一次動畫,只要加上參數 appear
就可以了
<Transition name="fade" appear>
<div v-if="show" class="box">something here</div>
</Transition>
transition mode
有時我們的元件使用了 v-if
+ v-else
,加上 transition 就會出現這個突兀的轉場
理想的狀況應該是第一個動畫結束後後,下一個動畫再跑,這時候就可以使用 mode="out-in"
來達成。另外也有 in-out
是下一個動畫先跑,第一個動畫再結束
<Transition name="fade" mode="out-in">
<div v-if="show" class="box">something here</div>
<div v-else class="box box2">something there</div>
</Transition>
transition-group
如果 transition 裡要顯示超過一個 DOM,就要改用 transition-group
,裡面的每個元素都要加上 key
且不可重複,如果希望這些元素有個外層 DOM 的話,可以加上 tag
<TransitionGroup name="list" tag="section">
<div v-for="id in idList" :key="id">
{{ id }}
</div>
</TransitionGroup>
Vue2 有預設的 wrapper tag
<span>
,但一樣可以設定其他tag
transition hooks
transition 本身也提供多個事件,可以在不同時間點執行特定的函式,enter
跟 leave
都各自有四種狀態,這邊以 enter 的來說明觸發時機:
- before-enter:動畫開始前,使用 v-if 的話 dom 尚未載入
- enter:動畫執行中,使用 v-if 的話 dom 已經載入
- after-enter:動畫結束後
- enter-cancelled:動畫取消後。例如:點了顯示按鈕後,區塊還沒完整顯示(還沒觸發 after-enter),就又按了關閉按鈕,這個狀況就會觸發 enter-cancelled 然後接著跑 before-leave
較特別的地方是 leave-cancelled
只有在搭配 v-show
時才可以使用,enter-cancelled
則沒有這個限制
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
nested transition
有時我們希望區塊出現時,父層跟子層分別有各自的動畫,這種狀況就會寫兩個 transition,舉例來說:我想做出區塊先淡入,文字再由上而下出現。於是我們很直覺的寫了兩層的 transition 並分別寫了不同的 name 和 css,但寫完之後會發現:咦,為何子層的動畫都沒跑到?看起來跟只寫一層 fade transition 的效果一樣阿
<button @click="show = !show">toggle</button>
<Transition name="fade">
<div v-if="show" class="box">
<Transition name="fade-down">
<div v-if="show">something here</div>
</Transition>
</div>
</Transition>
/* fade */
.fade-enter-active, .fade-leave-active {
transition: all 0.5s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
/* fade down */
.fade-down-enter-active, .fade-down-leave-active {
transition: all 0.5s;
}
.fade-down-enter-from, .fade-down-leave-to {
opacity: 0;
transform: translateY(-30px);
}
仔細想想其實也滿合理的,因為兩個 transision 裡都是用同個參數控制顯示 v-if="show"
,所以兩個動畫會同時跑,看起來就像沒有跑到,這時候就可以用到剛剛介紹的 transition hooks 了。首先,我們需要多設定一個參數來控制裡面的文字顯示,讓動畫可以分開跑,為了統一命名格式,我們把原本的 show
命名改成 initBox
(外層),新的參數叫 initText
(內層文字),顯示的時候,順序是先跑外層動畫,再跑內層動畫,所以可以在外層的 @enter
事件上將 initText
改為 true,讓外層動畫進行時再觸發內層文字的顯示:
<button @click="initBox = !initBox">toggle</button>
<Transition name="fade" @enter="initText = true">
<div v-if="initBox" class="box">
<Transition name="fade-down">
<div v-if="initText">something here</div>
</Transition>
</div>
</Transition>
效果會像這樣:
接下來處理消失的效果,因為內外層消失的動畫順序跟剛剛是相反的,所以 toggle 按鈕的 click 事件也需要改一下,點擊 toggle 時要先將 initText
改為 false,在子層 transition 的 @leave
事件上再把 initBox
改為 false,讓內層動畫進行時再更新外層動畫:
<button @click="handleToggle">toggle</button>
<Transition name="fade" @enter="initText = true">
<div v-if="initBox" class="box">
<Transition name="fade-down" @leave="initBox = false">
<div v-if="initText">something here</div>
</Transition>
</div>
</Transition>
const { ref } = Vue;
const App = {
setup() {
const initBox = ref(false);
const initText = ref(false);
const handleToggle = () => {
if (!initBox.value) initBox.value = true; // 顯示時先更新 initBox
else initText.value = false; // 消失時先更新 initText
}
return { initBox, initText, handleToggle };
}
}
效果如下,如果希望消失時兩個動畫再多一點時間差,內層可以改用 @after-leave
:
覺得意猶未盡的話可以玩玩看這個彈窗效果:因為彈窗可能會疊加多個,為了讓彈窗關閉的那個瞬間,背景遮罩可以先保留,彈窗完全關閉後再顯示下一層彈窗,所以用了巢狀的 transition,控制顯示的部分有結合彈窗陣列做一些處理,也歡迎討論或分享其他做法~