Vue 3.0 列表過渡

2021-07-16 11:35 更新

目前為止,關(guān)于過渡我們已經(jīng)講到:

  • 單個(gè)節(jié)點(diǎn)
  • 同一時(shí)間渲染多個(gè)節(jié)點(diǎn)中的一個(gè)

那么怎么同時(shí)渲染整個(gè)列表,比如使用 v-for?在這種場景中,使用 <transition-group> 組件。在我們深入例子之前,先了解關(guān)于這個(gè)組件的幾個(gè)特點(diǎn):

  • 不同于 <transition>,它會(huì)以一個(gè)真實(shí)元素渲染:默認(rèn)為一個(gè) <span>。你也可以通過 tag attribute 更換為其他元素。
  • 過渡模式不可用,因?yàn)槲覀儾辉傧嗷デ袚Q特有的元素。
  • 內(nèi)部元素總是需要提供唯一的 key attribute 值。
  • CSS 過渡的類將會(huì)應(yīng)用在內(nèi)部的元素中,而不是這個(gè)組/容器本身。

#列表的進(jìn)入/離開過渡

現(xiàn)在讓我們由一個(gè)簡單的例子深入,進(jìn)入和離開的過渡使用之前一樣的 CSS class 名。

  1. <div id="list-demo">
  2. <button @click="add">Add</button>
  3. <button @click="remove">Remove</button>
  4. <transition-group name="list" tag="p">
  5. <span v-for="item in items" :key="item" class="list-item">
  6. {{ item }}
  7. </span>
  8. </transition-group>
  9. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  5. nextNum: 10
  6. }
  7. },
  8. methods: {
  9. randomIndex() {
  10. return Math.floor(Math.random() * this.items.length)
  11. },
  12. add() {
  13. this.items.splice(this.randomIndex(), 0, this.nextNum++)
  14. },
  15. remove() {
  16. this.items.splice(this.randomIndex(), 1)
  17. }
  18. }
  19. }
  20. Vue.createApp(Demo).mount('#list-demo')

  1. .list-item {
  2. display: inline-block;
  3. margin-right: 10px;
  4. }
  5. .list-enter-active,
  6. .list-leave-active {
  7. transition: all 1s ease;
  8. }
  9. .list-enter-from,
  10. .list-leave-to {
  11. opacity: 0;
  12. transform: translateY(30px);
  13. }

點(diǎn)擊此處實(shí)現(xiàn)

這個(gè)例子有個(gè)問題,當(dāng)添加和移除元素的時(shí)候,周圍的元素會(huì)瞬間移動(dòng)到他們的新布局的位置,而不是平滑的過渡,我們下面會(huì)解決這個(gè)問題。

#列表的排序過渡

<transition-group> 組件還有一個(gè)特殊之處。不僅可以進(jìn)入和離開動(dòng)畫,還可以改變定位。要使用這個(gè)新功能只需了解新增的 v-move class,它會(huì)在元素的改變定位的過程中應(yīng)用。像之前的類名一樣,可以通過 name attribute 來自定義前綴,也可以通過 move-class attribute 手動(dòng)設(shè)置。

該 class 主要用于指定過渡 timing 和 easing 曲線,如下所示:

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" rel="external nofollow" ></script>
  2. <div id="flip-list-demo">
  3. <button @click="shuffle">Shuffle</button>
  4. <transition-group name="flip-list" tag="ul">
  5. <li v-for="item in items" :key="item">
  6. {{ item }}
  7. </li>
  8. </transition-group>
  9. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. items: [1, 2, 3, 4, 5, 6, 7, 8, 9]
  5. }
  6. },
  7. methods: {
  8. shuffle() {
  9. this.items = _.shuffle(this.items)
  10. }
  11. }
  12. }
  13. Vue.createApp(Demo).mount('#flip-list-demo')

  1. .flip-list-move {
  2. transition: transform 0.8s ease;
  3. }

點(diǎn)擊此處實(shí)現(xiàn)

這個(gè)看起來很神奇,內(nèi)部的實(shí)現(xiàn),Vue 使用了一個(gè)叫 FLIP 簡單的動(dòng)畫隊(duì)列使用 transforms 將元素從之前的位置平滑過渡新的位置。

我們將之前實(shí)現(xiàn)的例子和這個(gè)技術(shù)結(jié)合,使我們列表的一切變動(dòng)都會(huì)有動(dòng)畫過渡。

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js" rel="external nofollow" ></script>
  2. <div id="list-complete-demo" class="demo">
  3. <button @click="shuffle">Shuffle</button>
  4. <button @click="add">Add</button>
  5. <button @click="remove">Remove</button>
  6. <transition-group name="list-complete" tag="p">
  7. <span v-for="item in items" :key="item" class="list-complete-item">
  8. {{ item }}
  9. </span>
  10. </transition-group>
  11. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
  5. nextNum: 10
  6. }
  7. },
  8. methods: {
  9. randomIndex() {
  10. return Math.floor(Math.random() * this.items.length)
  11. },
  12. add() {
  13. this.items.splice(this.randomIndex(), 0, this.nextNum++)
  14. },
  15. remove() {
  16. this.items.splice(this.randomIndex(), 1)
  17. },
  18. shuffle() {
  19. this.items = _.shuffle(this.items)
  20. }
  21. }
  22. }
  23. Vue.createApp(Demo).mount('#list-complete-demo')

  1. .list-complete-item {
  2. transition: all 0.8s ease;
  3. display: inline-block;
  4. margin-right: 10px;
  5. }
  6. .list-complete-enter-from,
  7. .list-complete-leave-to {
  8. opacity: 0;
  9. transform: translateY(30px);
  10. }
  11. .list-complete-leave-active {
  12. position: absolute;
  13. }

點(diǎn)擊此處實(shí)現(xiàn)

TIP

需要注意的是使用 FLIP 過渡的元素不能設(shè)置為 display: inline。作為替代方案,可以設(shè)置為 display: inline-block 或者放置于 flex 中

FLIP 動(dòng)畫不僅可以實(shí)現(xiàn)單列過渡,多維網(wǎng)格也同樣可以過渡

TODO:示例

#列表的交錯(cuò)過渡

通過 data attribute 與 JavaScript 通信,就可以實(shí)現(xiàn)列表的交錯(cuò)過渡:

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js" rel="external nofollow" ></script>
  2. <div id="demo">
  3. <input v-model="query" />
  4. <transition-group
  5. name="staggered-fade"
  6. tag="ul"
  7. :css="false"
  8. @before-enter="beforeEnter"
  9. @enter="enter"
  10. @leave="leave"
  11. >
  12. <li
  13. v-for="(item, index) in computedList"
  14. :key="item.msg"
  15. :data-index="index"
  16. >
  17. {{ item.msg }}
  18. </li>
  19. </transition-group>
  20. </div>

  1. const Demo = {
  2. data() {
  3. return {
  4. query: '',
  5. list: [
  6. { msg: 'Bruce Lee' },
  7. { msg: 'Jackie Chan' },
  8. { msg: 'Chuck Norris' },
  9. { msg: 'Jet Li' },
  10. { msg: 'Kung Fury' }
  11. ]
  12. }
  13. },
  14. computed: {
  15. computedList() {
  16. var vm = this
  17. return this.list.filter(item => {
  18. return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
  19. })
  20. }
  21. },
  22. methods: {
  23. beforeEnter(el) {
  24. el.style.opacity = 0
  25. el.style.height = 0
  26. },
  27. enter(el, done) {
  28. gsap.to(el, {
  29. opacity: 1,
  30. height: '1.6em',
  31. delay: el.dataset.index * 0.15,
  32. onComplete: done
  33. })
  34. },
  35. leave(el, done) {
  36. gsap.to(el, {
  37. opacity: 0,
  38. height: 0,
  39. delay: el.dataset.index * 0.15,
  40. onComplete: done
  41. })
  42. }
  43. }
  44. }
  45. Vue.createApp(Demo).mount('#demo')

點(diǎn)擊此處實(shí)現(xiàn)

#可復(fù)用的過渡

過渡可以通過 Vue 的組件系統(tǒng)實(shí)現(xiàn)復(fù)用。要?jiǎng)?chuàng)建一個(gè)可復(fù)用過渡組件,你需要做的就是將 <transition> 或者 <transition-group> 作為根組件,然后將任何子組件放置在其中就可以了。

TODO:使用 Vue3 重構(gòu)

使用 template 的簡單例子:

  1. Vue.component('my-special-transition', {
  2. template: '\
  3. <transition\
  4. name="very-special-transition"\
  5. mode="out-in"\
  6. @before-enter="beforeEnter"\
  7. @after-enter="afterEnter"\
  8. >\
  9. <slot></slot>\
  10. </transition>\
  11. ',
  12. methods: {
  13. beforeEnter(el) {
  14. // ...
  15. },
  16. afterEnter(el) {
  17. // ...
  18. }
  19. }
  20. })

函數(shù)式組件更適合完成這個(gè)任務(wù):

  1. Vue.component('my-special-transition', {
  2. functional: true,
  3. render: function(createElement, context) {
  4. var data = {
  5. props: {
  6. name: 'very-special-transition',
  7. mode: 'out-in'
  8. },
  9. on: {
  10. beforeEnter(el) {
  11. // ...
  12. },
  13. afterEnter(el) {
  14. // ...
  15. }
  16. }
  17. }
  18. return createElement('transition', data, context.children)
  19. }
  20. })

#動(dòng)態(tài)過渡

在 Vue 中即使是過渡也是數(shù)據(jù)驅(qū)動(dòng)的!動(dòng)態(tài)過渡最基本的例子是通過 name attribute 來綁定動(dòng)態(tài)值。

  1. <transition :name="transitionName">
  2. <!-- ... -->
  3. </transition>

當(dāng)你想用 Vue 的過渡系統(tǒng)來定義的 CSS 過渡/動(dòng)畫在不同過渡間切換會(huì)非常有用。

所有過渡 attribute 都可以動(dòng)態(tài)綁定,但我們不僅僅只有 attribute 可以利用,還可以通過事件鉤子獲取上下文中的所有數(shù)據(jù),因?yàn)槭录^子都是方法。這意味著,根據(jù)組件的狀態(tài)不同,你的 JavaScript 過渡會(huì)有不同的表現(xiàn)

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow" ></script>
  2. <div id="dynamic-fade-demo" class="demo">
  3. Fade In:
  4. <input type="range" v-model="fadeInDuration" min="0" :max="maxFadeDuration" />
  5. Fade Out:
  6. <input
  7. type="range"
  8. v-model="fadeOutDuration"
  9. min="0"
  10. :max="maxFadeDuration"
  11. />
  12. <transition
  13. :css="false"
  14. @before-enter="beforeEnter"
  15. @enter="enter"
  16. @leave="leave"
  17. >
  18. <p v-if="show">hello</p>
  19. </transition>
  20. <button v-if="stop" @click="stop = false; show = false">
  21. Start animating
  22. </button>
  23. <button v-else @click="stop = true">Stop it!</button>
  24. </div>

  1. const app = Vue.createApp({
  2. data() {
  3. return {
  4. show: true,
  5. fadeInDuration: 1000,
  6. fadeOutDuration: 1000,
  7. maxFadeDuration: 1500,
  8. stop: true
  9. }
  10. },
  11. mounted() {
  12. this.show = false
  13. },
  14. methods: {
  15. beforeEnter(el) {
  16. el.style.opacity = 0
  17. },
  18. enter(el, done) {
  19. var vm = this
  20. Velocity(
  21. el,
  22. { opacity: 1 },
  23. {
  24. duration: this.fadeInDuration,
  25. complete: function() {
  26. done()
  27. if (!vm.stop) vm.show = false
  28. }
  29. }
  30. )
  31. },
  32. leave(el, done) {
  33. var vm = this
  34. Velocity(
  35. el,
  36. { opacity: 0 },
  37. {
  38. duration: this.fadeOutDuration,
  39. complete: function() {
  40. done()
  41. vm.show = true
  42. }
  43. }
  44. )
  45. }
  46. }
  47. })
  48. app.mount('#dynamic-fade-demo')

TODO:示例

最后,創(chuàng)建動(dòng)態(tài)過渡的最終方案是組件通過接受 props 來動(dòng)態(tài)修改之前的過渡。一句老話,唯一的限制是你的想象力。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)