首页 > 编程笔记 > JavaScript笔记
Vue transition-group组件实现列表过渡动画
通过 transition 元素,可以实现单个节点和多个节点在同一时间渲染过渡,同样,也可以使用 transition-group 组件,实现一个列表中多项的过渡。
相对 transition,transition-group 有以下几个特点:
使用 CSS 类名,实现列表的进入/离开过渡,代码如下:
单击上面样例的 Shuffle 案例,会将数字随机打乱,但是很快就替换完成,没有动画的感觉。
在 transition-group 中有个 v-move 属性,能改变定位。开发人员通过给 v-move 属性设置过渡的切换时机和过渡曲线,就可以实现动态的定位过渡。v-move 名称的命名规则同 v-enter、v-enter-active 等属性的规则一样(默认为 v- 前缀,否则就以 transition-group 的 name 属性的值作为前缀)。
在上面样例的 style 中,添加如下代码的 CSS 类,单击 Shuffle 按钮,就可以体会到定位的动态过渡效果,代码如下:
Vue.js 使用了一个叫作 FLIP 的简单的动画队列,使用 transforms 将元素从之前的位置平滑过渡到新的位置。使用 FLIP 技术,可完成位置完美的平滑过渡,代码如下:
FLIP 不仅可以实现单列过渡,还可以实现多维网格过渡,代码如下:
通过 data attribute 与 JavaScript 通信,可以实现列表的交错过渡,代码如下:
相对 transition,transition-group 有以下几个特点:
- 与 transition 不同,它会以一个真实元素呈现,默认为 span,也可以通过 tag 属性指定。
- 在 transition-group 中不存在过渡模式,因为在列表过渡中不存在切换的离开/进入。
- 内部元素,必须有一个唯一的 key 属性值。
- CSS 过渡的类,将会应用到内部的元素中,而不是组/容器本身。
使用 CSS 类名,实现列表的进入/离开过渡,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Vue.js --> <script src="../static/js/Vue.js" type="text/JavaScript"></script> <title>Demo vue</title> </head> <body> <!-- 定义 Vue.js 的视图 --> <div id="app"> <button v-on:click="add">Add</button> <button v-on:click="remove">Remove</button> <button @click="shuffle">Shuffle</button> <transition-group name="list" tag="div"> <span v-for="item in items" v-bind:key="item" class="list-item"> {{ item }} </span> </transition-group> </div> <script type="text/JavaScript"> // 创建 Vue.js 对象 const vm = new Vue({ el: "#app", data: { 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); }, shuffle: function () { this.items = _.shuffle(this.items); } } }); </script> <style> .list-item { display: inline-block; margin-right: 10px; } .list-enter-active, .list-leave-active { transition: all 1s; } .list-enter, .list-leave-to { /* .list-leave-active for below version 2.1.8 */ opacity: 0; transform: translateY(30px); } </style> </body> </html>通过代码可以了解,列表的过渡实现同单元素的过渡实现基本类似,除了将过渡元素改成了 transition-group 外。
单击上面样例的 Shuffle 案例,会将数字随机打乱,但是很快就替换完成,没有动画的感觉。
在 transition-group 中有个 v-move 属性,能改变定位。开发人员通过给 v-move 属性设置过渡的切换时机和过渡曲线,就可以实现动态的定位过渡。v-move 名称的命名规则同 v-enter、v-enter-active 等属性的规则一样(默认为 v- 前缀,否则就以 transition-group 的 name 属性的值作为前缀)。
在上面样例的 style 中,添加如下代码的 CSS 类,单击 Shuffle 按钮,就可以体会到定位的动态过渡效果,代码如下:
.list-move { transition: transform 3s; }
Vue.js 使用了一个叫作 FLIP 的简单的动画队列,使用 transforms 将元素从之前的位置平滑过渡到新的位置。使用 FLIP 技术,可完成位置完美的平滑过渡,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 引入 Vue.js --> <script src="../static/js/Vue.js" type="text/JavaScript"></script> <script src="../static/js/lodash.min.js" type="text/JavaScript"></script> <title>Demo vue</title> </head> <body> <!-- 定义 Vue.js 的视图 --> <div id="app"> <button v-on:click="shuffle">Shuffle</button> <button v-on:click="add">Add</button> <button v-on:click="remove">Remove</button> <transition-group name="list-complete" tag="p"> <span v-for="item in items" v-bind:key="item" class="list-complete-item"> {{ item }} </span> </transition-group> </div> <script type="text/JavaScript"> // 创建 Vue.js 对象 const vm = new Vue({ el: "#app", data: { 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); }, shuffle: function () { this.items = _.shuffle(this.items); } } }); </script> <style> .list-complete-item { transition: all 0.5s; display: inline-block; margin-right: 10px; } .list-complete-enter, .list-complete-leave-to { /* .list-complete-leave-active for below version 2.1.8 */ opacity: 0; transform: translateY(30px); } .list-complete-leave-active { position: absolute; } </style> </body> </html>需要注意的是使用 FLIP 过渡的元素不能被设置为 display:inline。作为替代方案,可以设置为 display:inline-block 或者放置于 flex 中。
FLIP 不仅可以实现单列过渡,还可以实现多维网格过渡,代码如下:
<!DOCTYPE html> <html> <head> <title>List Move Transitions Sudoku Example</title> <script src="../static/js/Vue.js" type="text/JavaScript"></script> <script src="./static/js/lodash.min.js" type="text/JavaScript"></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 0.5s; } </style> </head> <body> <div id="sudoku-demo" class="demo"> <h1>Lazy Sudoku</h1> <p>Keep hitting the shuffle button until you win.</p> <button @click="shuffle">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> <script> new Vue({ el: "#sudoku-demo", data: { cells: Array.apply(null, { length: 81 }).map(function(_, index) { return { id: index, number: (index % 9) + 1 }; }) }, methods: { shuffle: function() { this.cells = _.shuffle(this.cells); } } }); </script> </body> </html>
通过 data attribute 与 JavaScript 通信,可以实现列表的交错过渡,代码如下:
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script> <div id="staggered-list-demo"> <input type="text" v-model="query"> <transition-group name="staggered-fade" tag="ul" v-bind:class="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> new Vue({ el: '#staggered-list-demo', data: { 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" }, { duration: 300, complete: done }); }, delay); }, leave: function(el, done) { var delay = el.dataset.index * 150; setTimeout(function() { Velocity(el, { opacity: 0, height: 0 }, { complete: done }); }, delay); } } });