Vue 動畫
Vue 內建的 <Transition>
元件可以幫助我們在元素使用 v-if
、v-show
或動態元件新增或移除時執行動畫。
在其他情況下,使用純 CSS 過渡和動畫也沒有問題。
CSS 過渡和動畫簡介
但在我們使用 Vue 特有的內建 <Transition>
元件建立動畫之前,讓我們來看兩個如何使用純 CSS 動畫和過渡與 Vue 結合的示例。
示例
App.vue
:
<template>
<h1>Basic CSS Transition</h1>
<button @click="this.doesRotate = true">Rotate</button>
<div :class="{ rotate: doesRotate }"></div>
</template>
<script>
export default {
data() {
return {
doesRotate: false
}
}
}
</script>
<style scoped>
.rotate {
rotate: 160deg;
transition: rotate 1s;
}
div {
border: solid black 2px;
background-color: lightcoral;
width: 60px;
height: 60px;
}
h1, button, div {
margin: 10px;
}
</style>
執行示例 »
在上一個示例中,我們使用 v-bind
為 <div>
標籤添加了一個 class 以使其旋轉。旋轉耗時 1 秒是因為它透過 CSS 的 transition
屬性定義的。
在下面的示例中,我們展示瞭如何使用 CSS 的 animation
屬性來移動一個物件。
示例
App.vue
:
<template>
<h1>Basic CSS Animation</h1>
<button @click="this.doesMove = true">Start</button>
<div :class="{ move: doesMove }"></div>
</template>
<script>
export default {
data() {
return {
doesMove: false
}
}
}
</script>
<style scoped>
.move {
animation: move .5s alternate 4 ease-in-out;
}
@keyframes move {
from {
translate: 0 0;
}
to {
translate: 70px 0;
}
}
div {
border: solid black 2px;
background-color: lightcoral;
border-radius: 50%;
width: 60px;
height: 60px;
}
h1, button, div {
margin: 10px;
}
</style>
執行示例 »
The <Transition> 元件
在上面兩個示例中,使用純 CSS 過渡和動畫沒有問題。
但幸運的是,Vue 提供了內建的 <Transition>
元件,當元素使用 v-if
或 v-show
被新增到我們的應用程式或從應用程式中移除時,我們可以用它來動畫化,因為這用純 CSS 動畫很難實現。
首先,讓我們建立一個應用程式,其中一個按鈕可以新增或移除一個 <p>
標籤。
示例
App.vue
:
<template>
<h1>Add/Remove <p> Tag</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<p v-if="exists">Hello World!</p>
</template>
<script>
export default {
data() {
return {
exists: false
}
},
computed: {
btnText() {
if(this.exists) {
return 'Remove';
}
else {
return 'Add';
}
}
}
}
</script>
<style>
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
}
</style>
執行示例 »
現在,讓我們將 <Transition>
元件包裹在 <p>
標籤周圍,看看我們如何為 <p>
標籤的移除新增動畫。
當我們使用 <Transition>
元件時,我們會自動獲得六個不同的 CSS 類,可用於在元素新增或移除時進行動畫。
在下面的示例中,我們將使用自動可用的 v-leave-from
和 v-leave-to
類,在移除 <p>
標籤時建立一個淡出動畫。
示例
App.vue
:
<template>
<h1>Add/Remove <p> Tag</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<Transition>
<p v-if="exists">Hello World!</p>
</Transition>
</template>
<script>
export default {
data() {
return {
exists: false
}
},
computed: {
btnText() {
if(this.exists) {
return 'Remove';
}
else {
return 'Add';
}
}
}
}
</script>
<style>
.v-leave-from {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
transition: opacity 0.5s;
}
</style>
執行示例 »
六個 <Transition> 類
當我們使用 <Transition>
元件時,有六個類可供我們自動使用。
當 <Transition>
元件內的元素被新增時,我們可以使用前三個類來動畫化此過渡。
- v-enter-from
- v-enter-active
- v-enter-to
當 <Transition>
元件內的元素被移除時,我們可以使用接下來的三個類。
- v-leave-from
- v-leave-active
- v-leave-to
注意: <Transition>
元件的根級別只能有一個元素。
現在,讓我們使用這四個類,以便我們可以在 <p>
標籤被新增和移除時為其新增動畫。
示例
App.vue
:
<template>
<h1>Add/Remove <p> Tag</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<Transition>
<p v-if="exists">Hello World!</p>
</Transition>
</template>
<script>
export default {
data() {
return {
exists: false
}
},
computed: {
btnText() {
if(this.exists) {
return 'Remove';
}
else {
return 'Add';
}
}
}
}
</script>
<style>
.v-enter-from {
opacity: 0;
translate: -100px 0;
}
.v-enter-to {
opacity: 1;
translate: 0 0;
}
.v-leave-from {
opacity: 1;
translate: 0 0;
}
.v-leave-to {
opacity: 0;
translate: 100px 0;
}
p {
background-color: lightgreen;
display: inline-block;
padding: 10px;
transition: all 0.5s;
}
</style>
執行示例 »
我們還可以使用 v-enter-active
和 v-leave-active
來在元素新增時或移除時設定樣式或動畫。
示例
App.vue
:
<template>
<h1>Add/Remove <p> Tag</h1>
<button @click="this.exists = !this.exists">{{btnText}}</button><br>
<Transition>
<p v-if="exists">Hello World!</p>
</Transition>
</template>
<script>
export default {
data() {
return {
exists: false
}
},
computed: {
btnText() {
if(this.exists) {
return 'Remove';
}
else {
return 'Add';
}
}
}
}
</script>
<style>
.v-enter-active {
background-color: lightgreen;
animation: added 1s;
}
.v-leave-active {
background-color: lightcoral;
animation: added 1s reverse;
}
@keyframes added {
from {
opacity: 0;
translate: -100px 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
p {
display: inline-block;
padding: 10px;
border: dashed black 1px;
}
</style>
執行示例 »
Transition 'name' Prop
如果您有多個 <Transition>
元件,但希望其中至少一個 <Transition>
元件具有不同的動畫,則需要為 <Transition>
元件指定不同的名稱,以便區分它們。
我們可以使用 name
prop 來選擇 <Transition>
元件的名稱,這也會改變過渡類的名稱,因此我們可以為該元件設定不同的 CSS 動畫規則。
<Transition name="swirl">
如果過渡 name
prop 的值為 'swirl',則自動可用的類現在將以 'swirl-' 開頭,而不是 'v-'。
- swirl-enter-from
- swirl-enter-active
- swirl-enter-to
- swirl-leave-from
- swirl-leave-active
- swirl-leave-to
在下面的示例中,我們使用 name
prop 為 <Transition>
元件賦予不同的動畫。其中一個 <Transition>
元件沒有指定名稱,因此使用以 'v-' 開頭的自動生成的 CSS 類來設定動畫。另一個 <Transition>
元件被命名為 'swirl',以便它可以使用以 'swirl-' 開頭的自動生成的 CSS 類來設定動畫規則。
示例
App.vue
:
<template>
<h1>Add/Remove <p> Tag</h1>
<p>The second transition in this example has the name prop "swirl", so that we can keep the transitions apart with different class names.</p>
<hr>
<button @click="this.p1Exists = !this.p1Exists">{{btn1Text}}</button><br>
<Transition>
<p v-if="p1Exists" id="p1">Hello World!</p>
</Transition>
<hr>
<button @click="this.p2Exists = !this.p2Exists">{{btn2Text}}</button><br>
<Transition name="swirl">
<p v-if="p2Exists" id="p2">Hello World!</p>
</Transition>
</template>
<script>
export default {
data() {
return {
p1Exists: false,
p2Exists: false
}
},
computed: {
btn1Text() {
if(this.p1Exists) {
return 'Remove';
}
else {
return 'Add';
}
},
btn2Text() {
if(this.p2Exists) {
return 'Remove';
}
else {
return 'Add';
}
}
}
}
</script>
<style>
.v-enter-active {
background-color: lightgreen;
animation: added 1s;
}
.v-leave-active {
background-color: lightcoral;
animation: added 1s reverse;
}
@keyframes added {
from {
opacity: 0;
translate: -100px 0;
}
to {
opacity: 1;
translate: 0 0;
}
}
.swirl-enter-active {
animation: swirlAdded 1s;
}
.swirl-leave-active {
animation: swirlAdded 1s reverse;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 360deg;
scale: 1;
}
}
#p1, #p2 {
display: inline-block;
padding: 10px;
border: dashed black 1px;
}
#p2 {
background-color: lightcoral;
}
</style>
執行示例 »
JavaScript Transition Hooks
正如剛剛提到的,每個 Transition 類都對應一個事件,我們可以透過該事件來執行一些 JavaScript 程式碼。
Transition Class | JavaScript Event |
---|---|
v-enter-from | before-enter |
v-enter-active | enter |
v-enter-to | after-enter enter-cancelled |
v-leave-from | before-leave |
v-leave-active | leave |
v-leave-to | after-leave leave-cancelled (僅 v-show) |
在下面的示例中,當 after-enter
事件發生時,會執行一個方法,該方法會顯示一個紅色的 <div>
元素。
示例
App.vue
:
<template>
<h1>JavaScript Transition Hooks</h1>
<p>This code hooks into "after-enter" so that after the initial animation is done, a method runs that displays a red div.</p>
<button @click="pVisible=true">Create p-tag!</button><br>
<Transition @after-enter="onAfterEnter">
<p v-show="pVisible" id="p1">Hello World!</p>
</Transition>
<br>
<div v-show="divVisible">This appears after the "enter-active" phase of the transition.</div>
</template>
<script>
export default {
data() {
return {
pVisible: false,
divVisible: false
}
},
methods: {
onAfterEnter() {
this.divVisible = true;
}
}
}
</script>
<style scoped>
.v-enter-active {
animation: swirlAdded 1s;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 360deg;
scale: 1;
}
}
#p1, div {
display: inline-block;
padding: 10px;
border: dashed black 1px;
}
#p1 {
background-color: lightgreen;
}
div {
background-color: lightcoral;
}
</style>
執行示例 »
您可以使用下面的示例中的“切換”按鈕來中斷 <p>
元素的 enter transition 階段,從而觸發 enter-cancelled
事件。
示例
App.vue
:
<template>
<h1>The 'enter-cancelled' Event</h1>
<p>Click the toggle button again before the enter animation is finished to trigger the 'enter-cancelled' event.</p>
<button @click="pVisible=!pVisible">Toggle</button><br>
<Transition @enter-cancelled="onEnterCancelled">
<p v-if="pVisible" id="p1">Hello World!</p>
</Transition>
<br>
<div v-if="divVisible">You interrupted the "enter-active" transition.</div>
</template>
<script>
export default {
data() {
return {
pVisible: false,
divVisible: false
}
},
methods: {
onEnterCancelled() {
this.divVisible = true;
}
}
}
</script>
<style scoped>
.v-enter-active {
animation: swirlAdded 2s;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 720deg;
scale: 1;
}
}
#p1, div {
display: inline-block;
padding: 10px;
border: dashed black 1px;
}
#p1 {
background-color: lightgreen;
}
div {
background-color: lightcoral;
}
</style>
執行示例 »
The 'appear' Prop
如果我們有一個需要在頁面載入時進行動畫的元素,我們需要在 <Transition>
元件上使用 appear
prop。
<Transition appear>
...
</Transition>
在此示例中,appear
prop 在頁面首次載入時啟動動畫。
示例
App.vue
:
<template>
<h1>The 'appear' Prop</h1>
<p>The 'appear' prop starts the animation when the p tag below is rendered for the first time as the page opens. Without the 'appear' prop, this example would have had no animation.</p>
<Transition appear>
<p id="p1">Hello World!</p>
</Transition>
</template>
<style>
.v-enter-active {
animation: swirlAdded 1s;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 360deg;
scale: 1;
}
}
#p1 {
display: inline-block;
padding: 10px;
border: dashed black 1px;
background-color: lightgreen;
}
</style>
執行示例 »
Transition Between Elements
只要我們透過使用 <v-if>
和 <v-else-if>
來確保一次只顯示一個元素,<Transition>
元件也可以用於在多個元素之間切換。
示例
App.vue
:
<template>
<h1>Transition Between Elements</h1>
<p>Click the button to get a new image.</p>
<p>The new image is added before the previous is removed. We will fix this in the next example with mode="out-in".</p>
<button @click="newImg">Next image</button><br>
<Transition>
<img src="/img_pizza.svg" v-if="imgActive === 'pizza'">
<img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
<img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
<img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
<img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
</Transition>
</template>
<script>
export default {
data() {
return {
imgActive: 'pizza',
imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
indexNbr: 0
}
},
methods: {
newImg() {
this.indexNbr++;
if(this.indexNbr >= this.imgs.length) {
this.indexNbr = 0;
}
this.imgActive = this.imgs[this.indexNbr];
}
}
}
</script>
<style>
.v-enter-active {
animation: swirlAdded 1s;
}
.v-leave-active {
animation: swirlAdded 1s reverse;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 360deg;
scale: 1;
}
}
img {
width: 100px;
margin: 20px;
}
img:hover {
cursor: pointer;
}
</style>
執行示例 »
mode="out-in"
在上一個示例中,新的影像在舊影像被移除之前就被添加了。
我們使用 <Transition>
元件上的 mode="out-in"
prop 和 prop 值,以便在下一個元素被新增之前完成一個元素的移除。
示例
除了 mode="out-in"
之外,此示例還使用了計算值 'imgActive',而不是我們在上一個示例中使用的 'newImg' 方法。
App.vue
:
<template>
<h1>mode="out-in"</h1>
<p>Click the button to get a new image.</p>
<p>With mode="out-in", the next image is not added until the current image is removed. Another difference from the previous example, is that here we use computed prop instead of a method.</p>
<button @click="indexNbr++">Next image</button><br>
<Transition mode="out-in">
<img src="/img_pizza.svg" v-if="imgActive === 'pizza'">
<img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
<img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
<img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
<img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
</Transition>
</template>
<script>
export default {
data() {
return {
imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
indexNbr: 0
}
},
computed: {
imgActive() {
if(this.indexNbr >= this.imgs.length) {
this.indexNbr = 0;
}
return this.imgs[this.indexNbr];
}
}
}
</script>
<style>
.v-enter-active {
animation: swirlAdded 0.7s;
}
.v-leave-active {
animation: swirlAdded 0.7s reverse;
}
@keyframes swirlAdded {
from {
opacity: 0;
rotate: 0;
scale: 0.1;
}
to {
opacity: 1;
rotate: 360deg;
scale: 1;
}
}
img {
width: 100px;
margin: 20px;
}
img:hover {
cursor: pointer;
}
</style>
執行示例 »
Transition with Dynamic Components
我們還可以使用 <Transition>
元件來動畫化動態元件之間的切換。
示例
App.vue
:
<template>
<h1>Transition with Dynamic Components</h1>
<p>The Transition component wraps around the dynamic component so that the switching can be animated.</p>
<button @click="toggleValue = !toggleValue">Switch component</button>
<Transition mode="out-in">
<component :is="activeComp"></component>
</Transition>
</template>
<script>
export default {
data () {
return {
toggleValue: true
}
},
computed: {
activeComp() {
if(this.toggleValue) {
return 'comp-one'
}
else {
return 'comp-two'
}
}
}
}
</script>
<style>
.v-enter-active {
animation: slideIn 0.5s;
}
@keyframes slideIn {
from {
translate: -200px 0;
opacity: 0;
}
to {
translate: 0 0;
opacity: 1;
}
}
.v-leave-active {
animation: slideOut 0.5s;
}
@keyframes slideOut {
from {
translate: 0 0;
opacity: 1;
}
to {
translate: 200px 0;
opacity: 0;
}
}
#app {
width: 350px;
margin: 10px;
}
#app > div {
border: solid black 2px;
padding: 10px;
margin-top: 10px;
}
</style>
執行示例 »