列表过渡

126 阅读3分钟

目前为止关于过渡已涉及到单个节点和多个节点(每次只渲染一个),那么怎么同时渲染整个列表,比如使用v-for?在这种场景下,我们会使用transition-group元素组件,在深入例子之前,先了解关于这个组件的几个特点:

  • 默认情况下,它不会渲染一个包裹元素,但是可以通过tag attribute指定渲染一个元素
  • 过渡模式不可用,因为不再相互切换特有的元素
  • 内部元素总是需要提供唯一的key attribute值
  • CSS过渡的类将会应用在内部的元素中,而不是这个组/容器本身

1.列表的进入/离开过渡

我们由一个简单的例子深入:

  <transition-group name="list" tag="p">
    <span v-for="item in items" :key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
  methods: {
    randomIndex() {
      return Math.floor(Math.random() * this.items.length)
    },
    add() {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove() {
      this.items.splice(this.randomIndex(), 1)
    }
  }
.list-enter-active,
.list-leave-active {
  transition: all 1s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

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

2.列表的移动过渡

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

  <button @click="shuffle">Shuffle</button>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" :key="item">
      {{ item }}
    </li>
  </transition-group>
.flip-list-move {
  transition: transform 0.8s ease;
}
methods: {
    shuffle() {
      this.items = _.shuffle(this.items);
    }
  }

这个例子Vue内部使用了一个叫FLIP的动画技术,使得transform将元素从之前的位置平滑过渡到新的位置。 将这个技术和前面的例子结合起来,使列表的一切变动都会有动画过渡。 需要注意的是:使用FLIP过渡的元素不能设置为display:inline。作为替代方案,可以设置为display:inline-block或者将元素放置于flex布局中。 FLIP动画不仅可以实现单维度的过渡,多维网格中的元素也同样可以过渡。

3.列表的交错过渡

通过data attribute与JavaScript通信,就可以实现列表的交错过渡。

<transition-group
    name="staggered-fade"
    tag="ul"
    :css="false"
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <li
      v-for="(item, index) in computedList"
      :key="item.msg"
      :data-index="index"
    >
      {{ item.msg }}
    </li>
  </transition-group>
computed: {
    computedList() {
      var vm = this
      return this.list.filter(item => {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0
      el.style.height = 0
    },
    enter(el, done) {
      gsap.to(el, {
        opacity: 1,
        height: '1.6em',
        delay: el.dataset.index * 0.15,
        onComplete: done
      })
    },
    leave(el, done) {
      gsap.to(el, {
        opacity: 0,
        height: 0,
        delay: el.dataset.index * 0.15,
        onComplete: done
      })
    }
  }

4.可复用的过渡

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

Vue.component('my-special-transition', {
  template: '\
    <transition\
      name="very-special-transition"\
      mode="out-in"\
      @before-enter="beforeEnter"\
      @after-enter="afterEnter"\
    >\
      <slot></slot>\
    </transition>\
  ',
  methods: {
    beforeEnter(el) {
      // ...
    },
    afterEnter(el) {
      // ...
    }
  }
})

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

Vue.component('my-special-transition', {
  functional: true,
  render: function(createElement, context) {
    var data = {
      props: {
        name: 'very-special-transition',
        mode: 'out-in'
      },
      on: {
        beforeEnter(el) {
          // ...
        },
        afterEnter(el) {
          // ...
        }
      }
    }
    return createElement('transition', data, context.children)
  }
})

5.动态过渡

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

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

当已经定义了Vue的过渡类约定,并希望可以快速切换它们的时候,这会非常有用。尽管所有过渡attribute都可以动态绑定,我们可用的不仅有attribute。因为事件钩子是方法,它们可以访问任何上下文中的数据,这意味着根据组件的状态不同,JavaScript过渡可以有不同的表现。

  <transition
    :css="false"
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <p v-if="show">hello</p>
  </transition>
methods: {
    beforeEnter(el) {
      el.style.opacity = 0
    },
    enter(el, done) {
      var vm = this
      Velocity(
        el,
        { opacity: 1 },
        {
          duration: this.fadeInDuration,
          complete: function() {
            done()
            if (!vm.stop) vm.show = false
          }
        }
      )
    },
    leave(el, done) {
      var vm = this
      Velocity(
        el,
        { opacity: 0 },
        {
          duration: this.fadeOutDuration,
          complete: function() {
            done()
            vm.show = true
          }
        }
      )
    }
  }

最后,创建动态过渡的最终方案是组件通过接受prop来动态修改之前的过渡。一句老话,唯一的限制是你的想象力。