Vue.js - Transition过渡动画的使用4(使用transition-group实现列表过渡)
在之前的文章中我们都是使用 <transition> 组件来实现过渡, 其主要用于单个节点、或同一时间渲染多个节点中的一个这两种情况。而对于整个列表(比如使用 v-for)的过渡,则需要使用本文介绍的 <transition-group> 组件。
四、列表过渡
1,<transition-group> 说明
(1)不同于 <transition>,<transition-group> 会以一个真实元素呈现:默认为一个 <span>(我们可以通过 tag 特性更换为其他元素。)
(2)过渡模式不可用,因为我们不再相互切换特有的元素。
(3)<transition-group> 的内部元素总是需要提供唯一的 key 属性值。
2,列表的进入、离开过渡
(1)效果图
- 点击“插入一个元素”按钮,会在下方随机位置插入一个新的数字方块,新方块在插入过程中会有过渡动画。
- 点击“移除一个元素”按钮,会随机删除下方的一个数字方块,该方块在移除过程中会有过渡动画。
(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 部分稍作修改,可以发现在插入或移出过程中,其它元素也会从原来的位置平滑过渡新的位置。
<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 简单的动画队列实现排序过渡。所以即使没有插入或删除元素,对于元素顺序的变化,也是支持过渡动画的。
<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>
<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)同时在列表条目的显示或者移出过程中,会有相应的过渡动画。
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>