Vue v-for 動畫
Vue 內建的 <TransitionGroup>
元件可以幫助我們為使用 v-for
新增到頁面上的元素新增動畫。
<TransitionGroup> 元件
當使用 v-for
建立的元素被新增或移除時,我們可以將 <TransitionGroup>
元件用於這些元素周圍,為它們提供單獨的動畫。
在 <TransitionGroup>
元件內部使用 v-for
建立的標籤必須定義 key
屬性。
只有當我們透過使用 tag
屬性將其定義為特定標籤時,<TransitionGroup>
元件才會被渲染成一個 HTML 標籤,如下所示:
<TransitionGroup tag="ol">
<li v-for="x in products" :key="x">
{{ x }}
</li>
</TransitionGroup>
以下是上面程式碼在被 Vue 渲染後的結果:
<ol>
<li>Apple</li>
<li>Pizza</li>
<li>Rice</li>
</ol>
現在我們可以新增 CSS 程式碼來為新新增的專案設定動畫了。
<style>
.v-enter-from {
opacity: 0;
rotate: 180deg;
}
.v-enter-to {
opacity: 1;
rotate: 0deg;
}
.v-enter-active {
transition: all 0.7s;
}
</style>
在這個例子中,只需將新專案新增到 'products' 陣列中,就可以為它們新增動畫了。
示例
App.vue
:
<template>
<h3>The <TransitionGroup> Component</h3>
<p>New products are given animations using the <TransitionGroup> component.</p>
<input type="text" v-model="inpName">
<button @click="addEl">Add</button>
<TransitionGroup tag="ol">
<li v-for="x in products" :key="x">
{{ x }}
</li>
</TransitionGroup>
</template>
<script>
export default {
data() {
return {
products: ['Apple','Pizza','Rice'],
inpName: ''
}
},
methods: {
addEl() {
const el = this.inpName;
this.products.push(el);
this.inpName = null;
}
}
}
</script>
<style>
.v-enter-from {
opacity: 0;
rotate: 180deg;
}
.v-enter-to {
opacity: 1;
rotate: 0deg;
}
.v-enter-active {
transition: all 0.7s;
}
</style>
執行示例 »
新增和移除元素
當在其他元素之間移除元素時,其他元素將填補被移除元素的位置。要為當元素被移除時其他列表項的落位方式設定動畫,我們將使用自動生成的 v-move
類。
但首先,讓我們看看一個 **未** 為其他項在移除時落位方式設定動畫的例子:
示例
App.vue
:
<template>
<h3>The <TransitionGroup> Component</h3>
<p>New products are given animations using the <TransitionGroup> component.</p>
<button @click="addDie">Roll</button>
<button @click="removeDie">Remove random</button><br>
<TransitionGroup>
<div v-for="x in dice" :key="x" class="diceDiv" :style="{ backgroundColor: 'hsl('+x*40+',85%,85%)' }">
{{ x }}
</div>
</TransitionGroup>
</template>
<script>
export default {
data() {
return {
dice: [],
inpName: ''
}
},
methods: {
addDie() {
const newDie = Math.ceil(Math.random()*6);
this.dice.push(newDie);
},
removeDie() {
if(this.dice.length>0){
this.dice.splice(Math.floor(Math.random()*this.dice.length), 1);
}
}
},
mounted() {
this.addDie();
this.addDie();
this.addDie();
}
}
</script>
<style>
.v-enter-from {
opacity: 0;
translate: 200px 0;
rotate: 360deg;
}
.v-enter-to {
opacity: 1;
translate: 0 0;
rotate: 0deg;
}
.v-enter-active,
.v-leave-active {
transition: all 0.7s;
}
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
margin: 10px;
width: 30px;
height: 30px;
line-height: 30px;
vertical-align: middle;
text-align: center;
border: solid black 1px;
border-radius: 5px;
display: inline-block;
}
</style>
執行示例 »
如上例所示,當移除一個項時,移除項之後的項會突然跳到新的位置。要為當移除一個項時其他項的落位設定動畫,我們使用自動生成的 v-move
類。
v-move 類會在移除項離開時為其他元素設定動畫,但有一個問題:移除的項仍然存在並佔據空間,直到它被完全移除,因此 v-move 類將不會有任何效果。為了讓 v-move 類有東西可以動畫,我們可以將 position: absolute;
設定到 v-leave-active
類上。當移除期間設定了 position: absolute;
時,移除的項仍然可見,但不佔據空間。
在此示例中,與前一個示例的唯一區別是第 14 行和第 17 行新增的兩個新 CSS 類。
示例
App.vue
:
<style>
.v-enter-from {
opacity: 0;
translate: 200px 0;
rotate: 360deg;
}
.v-enter-to {
opacity: 1;
translate: 0 0;
rotate: 0deg;
}
.v-enter-active,
.v-leave-active,
.v-move {
transition: all 0.7s;
}
.v-leave-active { position: absolute; }
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
margin: 10px;
width: 30px;
height: 30px;
line-height: 30px;
vertical-align: middle;
text-align: center;
border: solid black 1px;
border-radius: 5px;
display: inline-block;
}
</style>
執行示例 »
一個更大的例子
讓我們以上面的例子為基礎來建立一個新的示例。
在這個例子中,當新增或移除新項,或者對整個陣列進行排序時,整個列表的動畫效果會更加清晰。
在這個例子中,我們可以
- 透過點選來移除項
- 排序項
- 在列表的隨機位置新增新項
示例
App.vue
:
<template>
<h3>The <TransitionGroup> Component</h3>
<p>Items inside the <TransitionGroup> component are animated when they are created or removed.</p>
<button @click="addDie">Roll</button>
<button @click="addDie10">Roll 10 dice</button>
<button @click="dice.sort(compareFunc)">Sort</button>
<button @click="dice.sort(shuffleFunc)">Shuffle</button><br>
<TransitionGroup>
<div
v-for="x in dice"
:key="x.keyNmbr"
class="diceDiv"
:style="{ backgroundColor: 'hsl('+x.dieNmbr*60+',85%,85%)' }"
@click="removeDie(x.keyNmbr)">
{{ x.dieNmbr }}
</div>
</TransitionGroup>
</template>
<script>
export default {
data() {
return {
dice: [],
keyNumber: 0
}
},
methods: {
addDie() {
const newDie = {
dieNmbr: Math.ceil(Math.random()*6),
keyNmbr: this.keyNumber
};
this.dice.splice(Math.floor(Math.random()*this.dice.length),0,newDie);
this.keyNumber++;
},
addDie10() {
for(let i=0; i<10; i++) {
this.addDie();
}
},
compareFunc(a,b){
return a.dieNmbr - b.dieNmbr;
},
shuffleFunc(a,b){
return Math.random()-0.5;
},
removeDie(key) {
const pos = this.dice.map(e => e.keyNmbr).indexOf(key);
this.dice.splice(pos, 1);
}
},
mounted() {
this.addDie10();
}
}
</script>
<style>
.v-enter-from {
opacity: 0;
scale: 0;
rotate: 360deg;
}
.v-enter-to {
opacity: 1;
scale: 1;
rotate: 0deg;
}
.v-enter-active,
.v-leave-active,
.v-move {
transition: all 0.7s;
}
.v-leave-active { position: absolute; }
.v-leave-from { opacity: 1; }
.v-leave-to { opacity: 0; }
.diceDiv {
margin: 10px;
width: 30px;
height: 30px;
line-height: 30px;
vertical-align: middle;
text-align: center;
border: solid black 1px;
border-radius: 5px;
display: inline-block;
}
.diceDiv:hover {
cursor: pointer;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#app {
position: relative;
}
</style>
執行示例 »