2.2.5 列表过渡
优质
小牛编辑
130浏览
2023-12-01
目前为止,关于过渡我们已经讲到:
- 单个节点
- 同一时间渲染多个节点中的一个
那么怎么同时渲染整个列表,比如使用v-for
?在这种场景中,使用<transition-group>
组件。在我们深入例子之前,先了解关于这个组件的几个特点:
- 不同于
<transition>
,它会以一个真实元素呈现:默认为一个<span>
。你也可以通过tag
特性更换为其他元素。 - 内部元素总是需要提供唯一的
key
属性值
列表的进入/离开过渡
现在让我们由一个简单的例子深入,进入和离开的过渡使用之前一样的 CSS 类名。
<div id="list-demo" class="demo"> <button v-on:click="add">Add</button> <button v-on:click="remove">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>
new Vue({ el: '#list-demo', 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) }, } })
.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); }.list-item {display: inline-block;margin-right:8px;} {{ item }}
这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,我们下面会解决这个问题。
列表的排序过渡
<transition-group>
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的v-move
特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过name
属性来自定义前缀,也可以通过move-class
属性手动设置。
v-move
对于设置过渡的切换时机和过渡曲线非常有用,你会看到如下的例子:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script> <div id="flip-list-demo" class="demo"> <button v-on:click="shuffle">Shuffle</button> <transition-group name="flip-list" tag="ul"> <li v-for="item in items" v-bind:key="item"> {{ item }} </li> </transition-group> </div>
new Vue({ el: '#flip-list-demo', data: { items: [1,2,3,4,5,6,7,8,9] }, methods: { shuffle: function () { this.items = _.shuffle(this.items) } } })
.flip-list-move { transition: transform 1s; }
这个看起来很神奇,内部的实现,Vue 使用了一个叫FLIP简单的动画队列使用 transforms 将元素从之前的位置平滑过渡新的位置。
我们将之前实现的例子和这个技术结合,使我们列表的一切变动都会有动画过渡。
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script> <div id="list-complete-demo" class="demo"> <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>
new Vue({ el: '#list-complete-demo', 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) } } })
.list-complete-item { transition: all 1s; 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; }{{ item }} .list-complete-item { transition: all 1s; display: inline-block; margin-right: 10px; } .list-complete-enter, .list-complete-leave-to { opacity: 0; transform: translateY(30px); } .list-complete-leave-active { position: absolute; }
需要注意的是使用 FLIP 过渡的元素不能设置为display: inline
。作为替代方案,可以设置为display: inline-block
或者放置于 flex 中
FLIP 动画不仅可以实现单列过渡,多维网格也同样可以过渡:
.sudoku-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 1s; }Lazy Sudoku
Keep hitting the shuffle button until you win.
{{ cell.number }}列表的交错过渡
通过 data 属性与 JavaScript 通信 ,就可以实现列表的交错过渡:
<script src="https://cdn.bootcss.com/velocity/1.2.3/velocity.js"></script> <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>
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' }, { complete: done } ) }, delay) }, leave: function (el, done) { var delay = el.dataset.index * 150 setTimeout(function () { Velocity( el, { opacity: 0, height: 0 }, { complete: done } ) }, delay) } } })