# 09. CSS 與 jQuery 動畫 (transition)
# 摘要
- 切換 DOM 而非資料來改變畫面:使用
v-for
和v-if
設定要顯示的 DOM 元素。 - 使用 CSS 製作過場動畫:使用
<transition>
和 class 改變樣式。 - 使用 jQuery 製作過場動畫:使用
<transition>
和 hooks 觸發事件。
# 切換 DOM 而非資料來改變畫面
在第四單元的事件綁定,點擊按鈕時,中間的文字會改變,是因為內容在 Vue 的 v-html
、 v-text
的控制下,這些內容能夠隨著資料的改變而被替換。
但如果在切換時要有過場動畫,使用 v-html
和 v-text
替換資料無法達成,需要 DOM 的顯示跟隱藏才能做出效果。
原本這部分的 HTML 結構如下:
<div class="menuItem white" >
<span class="number">{{ index + 1 }}</span>
<span class="type">{{ today.type }}</span>
<a :href="today.link" class="title">{{ today.title }}</a>
</div>
為了能在 DOM 上面加上動畫,要先用 v-for
把所有的資料都放上來,然後設定 v-if
讓條件符合者顯示,未符合者註解掉。
# 在 v-for 綁上 key 屬性
此外,Vue 在使用相同標籤元素的過場切換時,要使用 key
屬性,讓 Vue 可以辨識、區分他們,否則 Vue 只會替換其內部屬性而不會觸發過場效果。
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
這時候再用開發人員工具去看,會發現 menu
裡的所有資料都列了出來,只是大部分被註解掉。點擊按鈕時,不再是去改裡面的資料,而是將原本顯示的標籤註解掉,然後取消要顯示的資料的註解。
處理好之後,過場動畫的製作可以透過兩種方式:
- CSS
- JavaScript 函式庫 (這裡使用 jQuery)
# 使用 CSS 製作過場動畫
# transition 組件
在 Vue 裡面有提供一個叫 <transition>
的組件 (標籤),可以將被包起來的元素加上動畫效果。
首先,在要加上過場效果的地方用 <transition>
包起來。
<transition>
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
</transition>
# 表示元件不同狀態的六個過場 class
然後,在 CSS 使用 Vue 制定的 class 寫下自己要的過場效果:
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
在元件進入/離開的過程中,Vue 制定了六個 class 來表示元件進入/離開的不同狀態:
- v-enter
- v-enter-active
- v-enter-to
- v-leave
- v-leave-active
- v-leave-to
這六個 class 的關係如下圖:
(圖片來源及詳細說明:Vue - 进入/离开 & 列表过渡 (opens new window))
# name 屬性
這些過場用的 class 前面的 v-
是預設前綴,用於沒有名字的 <transition>
。
如果要自定義動畫的名稱,在使用 <transition>
的時候,要加上 name
屬性。例如 <transition name="fade">
。相對的,原本的 v-enter
也要替換為 fade-enter
。
<transition name="fade">
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
</transition>
# mode 屬性
這時候去操作畫面看效果,會看到切換時畫面上同時出現兩筆資料,但這不是我們想要的,我們希望螢幕上一次只會出現一筆資料。
解決方式是在 <transition>
裡面設定 mode="out-in"
,讓上一筆資料先離開,下一筆資料才會出現。
mode="out-in"
: 先出再進,這樣畫面上只會有一筆資料。mode="in-out"
: 先進再出,這樣畫面上還是會有兩筆資料。
# appear 屬性
如果希望載入網頁時,初始渲染的第一筆資料也能有過場效果,則在 <transition>
裡面加上 appear
屬性。
<transition name="fade" mode="out-in" appear>
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
</transition>
# 使用 jQuery 製作過場動畫
先載入 jQuery,然後將原本 <transition>
裡面的 name
屬性移除 (因為沒有用到 CSS)。
# JavaScript Hooks
用程式寫動畫的原理是事件,Vue 設計了幾組事件,使用 hooks 的概念來觸發對應的函式。
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
這些事件的概念和 CSS 的六個過場 class 類似。
回到 HTML,先在 <transition>
綁定這些事件和會觸發的對應函式:
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="afterLeave"
mode="out-in"
appear
>
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
</transition>
然後到 Vue 的 methods
去寫函式:
methods: {
changeIndex(index) {
this.index = (this.index + index + this.total) % this.total
},
beforeEnter(el){
$(el).css({opacity: 0})
},
enter(el,done){
$(el).animate({opacity: 1}, 1000, done)
},
afterEnter(el){
$(el).css({opacity: ''})
},
beforeLeave(el){
$(el).css({opacity: 1})
},
leave(el,done){
$(el).animate({opacity: 0}, 1000, done)
},
afterLeave(el){
$(el).css({opacity: ''})
}
}
說明:
- 在框架中,盡量避免手動讀取 DOM。可使用
el
參數來接收傳入的 DOM 物件。 - 在
enter
和leave
的函式中,要使用done
告訴程式這個動畫什麼時候完成。否則會被同步調用,過場會立即完成。 - 如果沒使用
afterEnter
清除樣式,那麼在enter
過程中加上去的opacity: 1
會殘留在那個元素裡,可能會造成意料之外的影響。
# v-bind:css="false"
用程式做動畫時,可能會和 CSS 的部分有衝突。為了避免受到 CSS 的影響,建議在 <transition>
加上 v-bind:css="false"
,以程式動畫為主。
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="afterLeave"
:css="false" // 避免程式的動畫效果受到 CSS 的影響
mode="out-in"
appear
>
<div class="menuItem white" v-for="(item,i) in menu" v-if="index == i" :key="item.title">
<span class="number">{{ i + 1 }}</span>
<span class="type">{{ item.type }}</span>
<a :href="item.link" class="title">{{ item.title }}</a>
</div>
</transition>