VUE transition-group 多列动画


多列动画使用的场景为父元素下有多个子元素的动画过渡场景,比如使用 v-for 在这种场景中,使用 <transition-group>组件。在深入例子之前,先了解关于这个组件的几个特点:
不同于<transition>,它会以一个真实元素呈现:默认为一个 。你也可以通过 tag 特性更换为其他元素。

  1. 过渡模式不可用,因为不再相互切换特有的元素。
  2. 内部元素 总是需要 提供唯一的 key 属性值。

列表的进入/离开过渡

现在由简单的例子深入,进入和离开的过渡使用之前一样的 CSS 类名。

  1. <div id="list-demo" class="demo">
  2. <button v-on:click="add">Add</button>
  3. <button v-on:click="remove">Remove</button>
  4. <transition-group name="list" tag="p">
  5. <span v-for="item in items" v-bind:key="item" class="list-item">
  6. {{ item }}
  7. </span>
  8. </transition-group>
  9. </div>
  1. new Vue({
  2. el: '#list-demo',
  3. data: {
  4. items: [1,2,3,4,5,6,7,8,9],
  5. nextNum: 10
  6. },
  7. methods: {
  8. randomIndex: function () {
  9. return Math.floor(Math.random() * this.items.length)
  10. },
  11. add: function () {
  12. this.items.splice(this.randomIndex(), 0, this.nextNum++)
  13. },
  14. remove: function () {
  15. this.items.splice(this.randomIndex(), 1)
  16. },
  17. }
  18. })
  1. .list-item {
  2. display: inline-block;
  3. margin-right: 10px;
  4. }
  5. .list-enter-active, .list-leave-active {
  6. transition: all 1s;
  7. }
  8. .list-enter, .list-leave-to
  9. /* .list-leave-active for below version 2.1.8 */ {
  10. opacity: 0;
  11. transform: translateY(30px);
  12. }

这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,下面会解决这个问题。

列表的排序过渡

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name 属性来自定义前缀,也可以通过 move-class 属性手动设置。

v-move 对于设置过渡的切换时机和过渡曲线非常有用,你会看到如下的例子:

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
  2. <div id="flip-list-demo" class="demo">
  3. <button v-on:click="shuffle">Shuffle</button>
  4. <transition-group name="flip-list" tag="ul">
  5. <li v-for="item in items" v-bind:key="item">
  6. {{ item }}
  7. </li>
  8. </transition-group>
  9. </div>
  1. new Vue({
  2. el: '#flip-list-demo',
  3. data: {
  4. items: [1,2,3,4,5,6,7,8,9]
  5. },
  6. methods: {
  7. shuffle: function () {
  8. this.items = _.shuffle(this.items)
  9. }
  10. }
  11. })
  1. .flip-list-move {
  2. transition: transform 1s;
  3. }

内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列使用 transforms 将元素从之前的位置平滑过渡新的位置。
将之前实现的例子和这个技术结合,使列表的一切变动都会有动画过渡。

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
  2. <div id="list-complete-demo" class="demo">
  3. <button v-on:click="shuffle">Shuffle</button>
  4. <button v-on:click="add">Add</button>
  5. <button v-on:click="remove">Remove</button>
  6. <transition-group name="list-complete" tag="p">
  7. <span
  8. v-for="item in items"
  9. v-bind:key="item"
  10. class="list-complete-item"
  11. >
  12. {{ item }}
  13. </span>
  14. </transition-group>
  15. </div>
  1. new Vue({
  2. el: '#list-complete-demo',
  3. data: {
  4. items: [1,2,3,4,5,6,7,8,9],
  5. nextNum: 10
  6. },
  7. methods: {
  8. randomIndex: function () {
  9. return Math.floor(Math.random() * this.items.length)
  10. },
  11. add: function () {
  12. this.items.splice(this.randomIndex(), 0, this.nextNum++)
  13. },
  14. remove: function () {
  15. this.items.splice(this.randomIndex(), 1)
  16. },
  17. shuffle: function () {
  18. this.items = _.shuffle(this.items)
  19. }
  20. }
  21. })
  1. .list-complete-item {
  2. transition: all 1s;
  3. display: inline-block;
  4. margin-right: 10px;
  5. }
  6. .list-complete-enter, .list-complete-leave-to
  7. /* .list-complete-leave-active for below version 2.1.8 */ {
  8. opacity: 0;
  9. transform: translateY(30px);
  10. }
  11. .list-complete-leave-active {
  12. position: absolute;
  13. }
  1. 需要注意的是使用 FLIP 过渡的元素不能设置为 display: inline 。作为替代方案,可以设置为 display: inline-block 或者放置于 flex

FLIP 动画不仅可以实现单列过渡,多维网格也同样可以过渡:

列表的交错过渡

通过 data 属性与 JavaScript 通信 ,就可以实现列表的交错过渡:

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  2. <div id="staggered-list-demo">
  3. <input v-model="query">
  4. <transition-group
  5. name="staggered-fade"
  6. tag="ul"
  7. v-bind:css="false"
  8. v-on:before-enter="beforeEnter"
  9. v-on:enter="enter"
  10. v-on:leave="leave"
  11. >
  12. <li
  13. v-for="(item, index) in computedList"
  14. v-bind:key="item.msg"
  15. v-bind:data-index="index"
  16. >{{ item.msg }}</li>
  17. </transition-group>
  18. </div>
  1. new Vue({
  2. el: '#staggered-list-demo',
  3. data: {
  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. computed: {
  14. computedList: function () {
  15. var vm = this
  16. return this.list.filter(function (item) {
  17. return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
  18. })
  19. }
  20. },
  21. methods: {
  22. beforeEnter: function (el) {
  23. el.style.opacity = 0
  24. el.style.height = 0
  25. },
  26. enter: function (el, done) {
  27. var delay = el.dataset.index * 150
  28. setTimeout(function () {
  29. Velocity(
  30. el,
  31. { opacity: 1, height: '1.6em' },
  32. { complete: done }
  33. )
  34. }, delay)
  35. },
  36. leave: function (el, done) {
  37. var delay = el.dataset.index * 150
  38. setTimeout(function () {
  39. Velocity(
  40. el,
  41. { opacity: 0, height: 0 },
  42. { complete: done }
  43. )
  44. }, delay)
  45. }
  46. }
  47. })

可复用的过渡

过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将 <transition> 或者 <transition-group> 作为根组件,然后将任何子组件放置在其中就可以了。

使用 template 的简单例子:

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

函数式组件更适合完成这个任务:

  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: function (el) {
  11. // ...
  12. },
  13. afterEnter: function (el) {
  14. // ...
  15. }
  16. }
  17. }
  18. return createElement('transition', data, context.children)
  19. }
  20. })

动态过渡

在 Vue 中即使是过渡也是数据驱动的!动态过渡最基本的例子是通过 name 特性来绑定动态值。

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

当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画 在不同过渡间切换会非常有用。

所有过渡特性都可以动态绑定,但我们不仅仅只有特性可以利用,还可以通过事件钩子获取上下文中的所有数据,因为事件钩子都是方法。这意味着,根据组件的状态不同,你的 JavaScript 过渡会有不同的表现。

  1. <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  2. <div id="dynamic-fade-demo" class="demo">
  3. Fade In: <input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
  4. Fade Out: <input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
  5. <transition
  6. v-bind:css="false"
  7. v-on:before-enter="beforeEnter"
  8. v-on:enter="enter"
  9. v-on:leave="leave"
  10. >
  11. <p v-if="show">hello</p>
  12. </transition>
  13. <button
  14. v-if="stop"
  15. v-on:click="stop = false; show = false"
  16. >Start animating</button>
  17. <button
  18. v-else
  19. v-on:click="stop = true"
  20. >Stop it!</button>
  21. </div>
  1. new Vue({
  2. el: '#dynamic-fade-demo',
  3. data: {
  4. show: true,
  5. fadeInDuration: 1000,
  6. fadeOutDuration: 1000,
  7. maxFadeDuration: 1500,
  8. stop: true
  9. },
  10. mounted: function () {
  11. this.show = false
  12. },
  13. methods: {
  14. beforeEnter: function (el) {
  15. el.style.opacity = 0
  16. },
  17. enter: function (el, done) {
  18. var vm = this
  19. Velocity(el,
  20. { opacity: 1 },
  21. {
  22. duration: this.fadeInDuration,
  23. complete: function () {
  24. done()
  25. if (!vm.stop) vm.show = false
  26. }
  27. }
  28. )
  29. },
  30. leave: function (el, done) {
  31. var vm = this
  32. Velocity(el,
  33. { opacity: 0 },
  34. {
  35. duration: this.fadeOutDuration,
  36. complete: function () {
  37. done()
  38. vm.show = true
  39. }
  40. }
  41. )
  42. }
  43. }
  44. })