详解Vue学习笔记进阶篇之列表过渡及其他
Chain 人气:0本文将介绍Vue中的列表过渡,动态过渡, 以及可复用过渡是实现。
列表过渡
目前为止,关于过渡我们已经讲到:
- 单个节点
- 同一时间渲染多个节点中的一个
那么怎么同时渲染整个列表,比如使用 v-for
?在这种场景中,使用 <transition-group>
组件。在我们深入例子之前,先了解关于这个组件的几个特点:
- 不同于 <transition>, 它会以一个真实元素呈现:默认为一个<span>。你也可以通过 tag 特性更换为其他元素。
- 内部元素
总是需要
提供唯一的key
属性值.列表的进入和离开过渡
现在让我们由一个简单的例子深入,进入和离开的过渡使用之前一样的 CSS 类名。
<div id="app1"> <button @click="add">Add</button> <button @click="remove">Remove</button> <transition-group name="list" tag="p"> <span v-for="item in items" :key="item" class="list-item"> {{item}} </span> </transition-group> </div>
.list-item{ display: inline-block; margin-right: 10px; } .list-enter-active, .list-leave-active{ transition: all 1s; } .list-enter, .list-leave-to{ opacity: 0; transform: translateY(30px); }
var app1 = new Vue({ el:'#app1', 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) } } })
运行结果:
这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,我们下面会解决这个问题。
列表的位移过渡
<transition-group>
组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的v-move
特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name
属性来自定义前缀,也可以通过 move-class
属性手动设置。
v-move
对于设置过渡的切换时机和过渡曲线非常有用,你会看到如下的例子:
<div id="app2"> <button @click="shuffle">Shuffle</button> <transition-group name="flip-list" tag="ul"> <li v-for="item in items" :key="item"> {{item}} </li> </transition-group> </div>
.flip-list-move { transition: transform 1s; }
var app2 = new Vue({ el:'#app2', data:{ items:[1,2,3,4,5,6,7,8,9] }, methods:{ shuffle:function () { this.items = _.shuffle(this.items) } } })
这个例子需要添加以下引用
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
运行结果:
这个看起来很神奇,内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列
使用 transforms 将元素从之前的位置平滑过渡新的位置。
我们将之前实现的例子和这个技术结合,使我们列表的一切变动都会有动画过渡。
<div id="app3" class="demo"> <button @click="shuffle">Shuffle</button> <button @click="add">Add</button> <button @click="remove">Remove</button> <transition-group name="list-complete" tag="p"> <span v-for="item in items" :key="item" class="list-complete-item"> {{item}} </span> </transition-group> </div>
.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; }
var app3 = new Vue({ el:'#app3', data:{ items:[1,2,3,4,5,6,7,8,9], nextNum:10 }, methods:{ shuffle:function () { this.items = _.shuffle(this.items) }, 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) } } })
运行结果:
列表的渐进过渡
通过 data 属性与 JavaScript 通信 ,就可以实现列表的渐进过渡:
<div id="app4"> <input v-model="query"> <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> </div>
var app4 = new Vue({ el:'#app4', data:{ query:'', list:[ {msg:'Bruce Lee'}, {msg:'Jackie Chan'}, {msg:'Chuck Norris'}, {msg:'Jet Li'}, {msg:'Kung Furry'}, {msg:'Chain Zhang'}, {msg:'Iris Zhao'}, ] }, 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) } } })
上述js代码需要添加对Velocity引用:
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
运行结果如下:
可复用的过渡
过渡可以通过 Vue 的组件系统实现复用。要创建一个可复用过渡组件,你需要做的就是将<transition>或者 <transition-group>作为根组件,然后将任何子组件放置在其中就可以了。
下面的例子是将上一个列表渐进过渡的例子改为可复用的过渡的源码:
<div id="app5"> <input v-model="query"> <my-transition :query="query" :list="list"> <li v-for="(item, index) in computedList" :key="item.msg" :data-index="index"> {{item.msg}} </li> </my-transition> </div>
Vue.component('my-transition', { template:` <transition-group name="staggered-fade" tag="ul" :css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave"> <slot></slot> </transition-group>`, props:['query', 'list'], 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) } } }) var app5 = new Vue({ el:'#app5', data:{ query:'', list:[ {msg:'Bruce Lee'}, {msg:'Jackie Chan'}, {msg:'Chuck Norris'}, {msg:'Jet Li'}, {msg:'Kung Furry'}, {msg:'Chain Zhang'}, {msg:'Iris Zhao'}, ] }, computed:{ computedList:function () { var vm = this return this.list.filter(function (item) { return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1 }) } }, })
效果与上一个例子一致:
但是函数组件更适合完成这个任务。由于暂时还没有学到render函数,所以暂时先不实现render函数组件。后面学到的时候再做打算。
动态过渡
在 Vue 中即使是过渡也是数据驱动的!动态过渡最基本的例子是通过 name 特性来绑定动态值。
<transition v-bind:name="transitionName"> <!-- ... --> </transition>
当你想用 Vue 的过渡系统来定义的 CSS 过渡/动画 在不同过渡间切换会非常有用。
所有的过渡特性都是动态绑定。它不仅是简单的特性,通过事件的钩子函数方法,可以在获取到相应上下文数据。这意味着,可以根据组件的状态通过 JavaScript 过渡设置不同的过渡效果。
<div id="app6"> Fade In: <input type="range" v-model="fadeInDuration" min="0" :max="maxFadeDuration"> Fade Out: <input type="range" v-model="fadeOutDuration" min="0" :max="maxFadeDuration"> <transition v-bind:css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave"> <p v-if="show">hello chain</p> </transition> <button @click="stop = true">Stop it</button> </div>
var app6 = new Vue({ el: '#app6', data: { show: true, fadeInDuration: 1000, fadeOutDuration: 1000, maxFadeDuration: 1500, stop: false }, mounted: function () { this.show = false }, methods: { beforeEnter: function (el) { el.style.opacity = 0 }, enter: function (el, done) { var vm = this Velocity(el, { opacity: 1 }, { duration: this.fadeInDuration, complete: function () { done() if (!vm.stop) vm.show = false } } ) }, leave: function (el, done) { var vm = this Velocity(el, { opacity: 0 }, { duration: this.fadeOutDuration, complete: function () { done() vm.show = true } } ) } } })
运行结果:
其中例子里的mounted是在Vue挂载完成,也就是模板中的html渲染到html页面中时的一个钩子函数,只会执行一次。具体内容可以理解下Vue的生命周期,这里就不赘述了。
但是如果这里不使用mounted的话,也是可以用初始渲染来实现,只不过比较麻烦。实现的方法是:
在transition中加入appear钩子函数:@appear="appear",然后在vue实例的methods中添加appear方法:
appear: function (el, done) { var vm = this Velocity(el, { opacity: 1 }, { duration: this.fadeInDuration, complete: function () { done() if (!vm.stop) vm.show = false } } ) }
加载全部内容