Vue Animations với v-for
Thành phần <TransitionGroup>
tích hợp trong Vue giúp chúng ta tạo hiệu ứng động cho các phần tử được thêm vào trang của mình bằng v-for
.
Thành phần <TransitionGroup>
Thành phần <TransitionGroup>
được sử dụng xung quanh các phần tử được tạo bằng v-for
, để cung cấp cho các phần tử này các hình ảnh động riêng lẻ khi chúng được thêm hoặc xóa.
Các thẻ được tạo bằng v-for
bên trong thành phần <TransitionGroup>
phải được xác định bằng thuộc tính key
.
Thành phần <TransitionGroup>
chỉ được hiển thị dưới dạng thẻ HTML nếu chúng ta xác định nó là một thẻ cụ thể bằng cách sử dụng tag
prop, như sau:
<TransitionGroup tag="ol">
<li v-for="x in products" :key="x">
{{ x }}
</li>
</TransitionGroup>
Đây là kết quả từ đoạn mã trên, sau khi được Vue hiển thị:
<ol>
<li>Apple</li>
<li>Pizza</li>
<li>Rice</li>
</ol>
Bây giờ chúng ta có thể thêm mã CSS để tạo hiệu ứng cho các mục mới khi chúng được thêm vào danh sách:
<style>
.v-enter-from {
opacity: 0;
rotate: 180deg;
}
.v-enter-to {
opacity: 1;
rotate: 0deg;
}
.v-enter-active {
transition: all 0.7s;
}
</style>
Trong ví dụ này, các mục mới sẽ được tạo hoạt ảnh đơn giản bằng cách thêm chúng vào mảng 'sản phẩm':
Ví dụ
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>
Chạy ví dụ »Thêm và xóa phần tử
Khi loại bỏ các phần tử ở giữa các phần tử khác, các phần tử khác sẽ rơi vào vị trí của phần tử bị xóa. Để tạo hiệu ứng cho các mục còn lại trong danh sách được sắp xếp đúng vị trí khi một phần tử bị xóa, chúng ta sẽ sử dụng lớp v-move
được tạo tự động.
Nhưng trước tiên, hãy xem một ví dụ trong đó nó không hoạt hình về cách các mục khác rơi vào vị trí khi một phần tử bị xóa:
Ví dụ
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>
Chạy ví dụ » Như bạn có thể thấy trong ví dụ trên, khi một vật phẩm bị loại bỏ, các vật phẩm sau vật phẩm bị loại bỏ sẽ đột ngột nhảy vào vị trí mới. Để tạo hoạt ảnh cho phần còn lại của các mục khi một mục bị xóa, chúng tôi sử dụng lớp v-move
được tạo tự động.
Lớp v-move
tạo hoạt ảnh cho các phần tử khác khi mục bị xóa rời đi, nhưng có một vấn đề: Mục bị xóa vẫn tồn tại và chiếm vị trí cho đến khi nó bị xóa, và do đó lớp v-move
sẽ không có tác dụng gì. Để tạo hoạt ảnh cho lớp v-move
, chúng ta có thể đặt position: absolute;
đến lớp v-leave-active
. Khi position: absolute;
được đặt trong thời gian xóa, mục đã xóa vẫn hiển thị nhưng không chiếm vị trí.
Trong ví dụ này, điểm khác biệt duy nhất so với ví dụ trước là hai lớp CSS mới được thêm vào dòng 14 và 17:
Ví dụ
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>
Chạy ví dụ »Một ví dụ lớn hơn
Hãy sử dụng ví dụ trên làm cơ sở cho một ví dụ mới.
Trong ví dụ này, nó càng trở nên rõ ràng hơn về cách toàn bộ danh sách được tạo hình động khi một mục mới được thêm hoặc xóa hoặc khi toàn bộ mảng được sắp xếp.
Trong ví dụ này chúng ta có thể:
- Xóa các mục bằng cách nhấp vào chúng
- Sắp xếp các mục
- Thêm các mục mới vào một vị trí ngẫu nhiên trong danh sách
Ví dụ
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>
Chạy ví dụ »