在之前的文章中我们都是使用 <transition> 组件来实现过渡, 其主要用于单个节点、或同一时间渲染多个节点中的一个这两种情况。而对于整个列表(比如使用 v-for)的过渡,则需要使用本文介绍的 <transition-group> 组件。

四、列表过渡

1,<transition-group> 说明

(1)不同于 <transition><transition-group> 会以一个真实元素呈现:默认为一个 <span>(我们可以通过 tag 特性更换为其他元素。)
(2)过渡模式不可用,因为我们不再相互切换特有的元素。
(3)<transition-group> 的内部元素总是需要提供唯一的 key 属性值。

2,列表的进入、离开过渡

(1)效果图

  • 点击“插入一个元素”按钮,会在下方随机位置插入一个新的数字方块,新方块在插入过程中会有过渡动画。
  • 点击“移除一个元素”按钮,会随机删除下方的一个数字方块,该方块在移除过程中会有过渡动画。
    image.png

(2)样例代码

<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="add">插入一个元素</button>
    <button v-on:click="remove">移除一个元素</button>
    <transition-group name="list" tag="p">
      <span v-for="item in items" v-bind:key="item" class="list-item">
        {{ item }}
      </span>
    </transition-group>
  </div>
  </div>
</template>
 
<script>
export default {
  name: 'App',
  data: function(){
    return {
      items: [1,2,3,4,5,6,7,8,9],
      nextNum: 10
    }
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
}
</script>
 
<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /** 插入过程 **/
  .list-enter-active{
    transition: all 1s;
  }
  /** 移除过程 **/
  .list-leave-active {
    transition: all 1s;
  }
  /*** 开始插入、移除结束的位置变化 ***/
  .list-enter, .list-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }
</style>

3,列表的排序过渡

(1)上面的样例有个问题:虽然新插入的元素或者被移除的元素有动画效果,但它周围的元素会瞬间移动到他们新布局的位置,而不是平滑的过渡。要解决这个问题则需要借助新增的 v-move 特性。

v-move 特性会在元素改变定位的过程中应用,它像之前的类名一样:
可以通过 name 属性来自定义前缀(比如 name="xxxx",那么对应的类名便是 xxx-move)
也可以通过 move-class 属性手动设置自定义类名。

(2)这里对之前样例的 css 部分稍作修改,可以发现在插入或移出过程中,其它元素也会从原来的位置平滑过渡新的位置。

image.png

<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /** 插入过程 **/
  .list-enter-active{
    transition: all 1s;
  }
  /** 移除过程 **/
  .list-leave-active {
    transition: all 1s;
    position: absolute;
  }
  /*** 开始插入、移除结束的位置变化 ***/
  .list-enter, .list-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }
  /*** 元素定位改变时动画 ***/
  .list-move {
    transition: transform 1s;
  }
</style>

(3)Vue 使用了一个叫 FLIP 简单的动画队列实现排序过渡。所以即使没有插入或删除元素,对于元素顺序的变化,也是支持过渡动画的。

image.png

<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="shuffle">乱序</button>
    <transition-group name="list" tag="p">
      <span v-for="item in items" v-bind:key="item" class="list-item">
        {{ item }}
      </span>
    </transition-group>
  </div>
  </div>
</template>
 
<script>
export default {
  name: 'App',
  data: function(){
    return {
      items: [1,2,3,4,5,6,7,8,9]
    }
  },
  methods: {
    shuffle: function () {
      return this.items.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
    }
  }
}
</script>
 
<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /*** 元素定位改变时动画 ***/
  .list-move {
    transition: transform 1s;
  }
</style>

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

image.png

<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="shuffle">乱序</button>
    <transition-group name="cell" tag="div" class="container">
      <div v-for="cell in cells" :key="cell.id" class="cell">
        {{ cell.number }}
      </div>
    </transition-group>
  </div>
  </div>
</template>
 
<script>
export default {
  name: 'App',
  data: function(){
    return {
      cells: Array.apply(null, { length: 81 })
        .map(function (_, index) {
            return {
            id: index,
            number: index % 9 + 1
          }
        })
    }
  },
  methods: {
    shuffle: function () {
      this.cells.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
    }
  }
}
</script>
 
<style>
  .container {
    display: flex;
    flex-wrap: wrap;
    width: 238px;
    margin-top: 10px;
  }
  .cell {
    display: flex;
    justify-content: space-around;
    align-items: center;
    width: 25px;
    height: 25px;
    border: 1px solid #aaa;
    margin-right: -1px;
    margin-bottom: -1px;
  }
  .cell:nth-child(3n) {
    margin-right: 0;
  }
  .cell:nth-child(27n) {
    margin-bottom: 0;
  }
  .cell-move {
    transition: transform 20s;
  }
</style>
附:使用 js 钩子函数实现列表的交错过渡

我们也可以通过 data 属性与 JavaScript 通信,实现列表的交错过渡。

1,效果图

(1)在上方输入框中输入内容时,下方的列表会实时筛选并显示出包含该文字的条目。
(2)同时在列表条目的显示或者移出过程中,会有相应的过渡动画。

image.png

2,样例代码

<template>
  <div id="app">
    <div id="staggered-list-demo">
      <input v-model="query">
      <transition-group
        name="staggered-fade"
        tag="ul"
        v-bind:css="false"
        v-on:before-enter="beforeEnter"
        v-on:enter="enter"
        v-on:leave="leave">
        <li
          v-for="(item, index) in computedList"
          v-bind:key="item.msg"
          v-bind:data-index="index"
        >{{ item.msg }}</li>
      </transition-group>
    </div>
  </div>
</template>
 
<script>
import  Velocity from 'velocity-animate'
 
export default {
  name: 'App',
  data: function(){
    return {
      query: '',
      list: [
        { msg: 'Bruce Lee' },
        { msg: 'Jackie Chan' },
        { msg: 'Chuck Norris' },
        { msg: 'Jet Li' },
        { msg: 'Kung Fury' }
      ]
    }
  },
  computed: {
    computedList: function () {
      var vm = this
      return this.list.filter(function (item) {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.height = 0
    },
    enter: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 1, height: '1.6em' },
          { complete: done , duration: 20000 }
        )
      }, delay)
    },
    leave: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done , duration: 20000 }
        )
      }, delay)
    }
  }
}
</script>

爱吃鸡蛋饼
55 声望8 粉丝