自定义过渡的类名
另一种情况和Vue过渡执行过程见初始渲染
new Vue({
el: '#app-2',
data: {
taxiCalled: false
}
})
<style>
.slideInRight {
transform: translateX(300px);
}
.go {
transition: all 2s ease-out;
}
</style>
<div id="app-2">
<button @click="taxiCalled = true">
Call a cab
</button>
<transition enter-class="slideInRight" enter-active-class="go">
<p v-if="taxiCalled">?</p>
</transition>
</div>
这个过程到底发生了什么?
当点击按钮时,texiCalled
被设置成true
,并且taxi
插入页面。实际上在完成这些动作之前,Vue读取了指定的类enter-class
(这里为slideInRight
)并把它应用于taxi
的外包元素p
,然后指定类enter-active-class
的情况是一样的。
类enter-class
在第一帧之后就被移除了,类enter-active-class
也在动画结束时被移除。
这种创建动画的方式成为FLIP
- First(F):动画第一帧的状态;这里既是
taxi
在屏幕的右边开始。 - Last(L): 动画最后帧的状态;这里
taxi
在屏幕的最左边作为结束。 - Invert(I): 使用
transform
和opacity
在第一帧和最后帧两个状态之间扭转,这里使用translateX(300px)
使taxi
在两个状态间产生300px的位移。 - Play(p): 为我们设置的属性创建过渡,这里在2s内使
taxi
从右到左产生了300px的位移。
在钩子中使用Velocity
new Vue({
el: '#app-3',
data: {
taxiCalled: false
},
methods: {
enter(el, done) {
Velocity(el,
{ opacity: [1, 0], translateX: ["0px", "200px"] },
{ duration: 2000, easing: "ease-out", complete: done })
},
leave(el, done) {
Velocity(el,
{ opacity: [0, 1], 'font-size': ['0.1em', '1em'] },
{ duration: 200, complete: done })
}
}
})
<div id="app-3">
<button @click="taxiCalled = true">
Call a cab
</button>
<button @click="taxiCalled = false">
Cancel
</button>
<transition @enter="enter" @leave="leave" :css="false">
<p v-if="taxiCalled">?</p>
</transition>
</div>
抓取enter
、leave
和appear
三种情况的时间点,每种情况上都定义了四种事件,总计12种事件(见API)。在这12种事件绑定钩子函数,这些函数可以配合CSS模式使用,也可以单独使用。这里我们在钩子函数中使用velocity
脚本动画引擎,单独完成动画配置。
在这些钩子中不一定非要使用velocity
,可以使用任何库。
上面代码中:css="false"
是告诉Vue,我们关闭CSS的处理,这里可以节省点CPU时间,将跳过所有与CSS动画相关代码,单纯的使用Javascript动画。
我们这里分别在@enter
和@leave
上绑定了钩子函数,他们将在taxi
被插入和离开时执行。函数的第一个参数为外包标签(这里是<p>
),第二个参数done
必须写,尽管你可能不去用它。这是因为用Javascript代替CSS,Vue无法识别动画什么时候完成,这里Vue会认为这个离开的动画在@leave
事件之前就完成了,所以执行leave
没有动画渲染。
关于velocity
,这种类型的API,我们称为forcefeeding,我们只要往它的实例中填写数据,不管他内部如何运行。(具体查看velocity)
初始渲染
在<transition>
使用appear
特性,在组件第一次被插入时执行相关的转换。
使用它会给人带来一种页面很快的加载大量元素的感觉(错觉)。
这里还使用了<transition>
的name
特性,可以使用自定义的类名('自定义过渡的类名'的相似写法),name-enter-active
、name-enter
等。
而使用这种自定义类名是一种好习惯。
<div id="app-4">
<transition appear name="flip">
<!-- 指定宽高因为是引用的图片,不知道尺寸 -->
<img src="https://b-ssl.duitang.com/uploads/item/201602/20/20160220213530_Z4reH.thumb.700_0.jpeg" style="width:300px;height:300px">
</transition>
<p>在组件第一次插入文档时执行相关的过渡</p>
</div>
<script>
new Vue({
el: '#app-4'
})
</script>
img {
float: left;
padding: 5px
}
.flip-enter-active {
transition: all 5s cubic-bezier(0.55, 0.085, 0.68, 0.53);
}
.flip-enter {
transform: scaleY(0) translateZ(0);
opacity: 0;
}
以上整个过程如下:
Vue发现appear
特性,开始查找<transition>
标签中的JavaScript钩子或指定的CSS类名。之后如果有一个name
被指定,就根据这个name
查到过渡的入口:mySpecifiedName-enter
、mySpecifiedName-enter-active
等。
如果以上过程失败,就会找默认的v-enter
、v-enter-active
等。
两个元素之间的过渡
new Vue({
el:'#app-5',
data:{
kisses: 0
}
})
#app-5 button span {
color: rgb(255, 0, 140);
}
#app-5 p {
margin: 0;
position: absolute;
font-size: 3em;
}
.fade-enter-active {
transition: opacity 5s
}
.fade-leave-active {
transition: opacity 5s;
opacity: 0
}
.fade-enter {
opacity: 0
}
<div id="app-5">
<button @click="kisses++">
<span>?</span>Kiss!</button>
<transition name="fade">
<p v-if="kisses < 3">? frog</p>
<p v-if="kisses >= 3">? princess</p>
</transition>
</div>
结果发现切换后没任何过渡效果,原因是什么呢?
原来是,Vue会启动自身的优化系统,发现两个元素一模一样,就是内容不同,因此当切换时,只做了内容的替换,标签<p></p>
部分并没有被替换,因此没有过渡的效果。
我们可以为需要过渡的元素增加key
属性,让Vue识别青蛙与公主两个不同的元素。如下:
<transition name="fade">
<p v-if="kisses < 1" key="frog">? frog</p>
<p v-if="kisses >= 1" key="princess">? princess</p>
</transition>
过渡效果正常了。
最佳实践,在元素上使用key
,尤其是当元素间具有不同的语义时。
多个元素之间的过渡和过渡模式
在有两个以上元素时,你可能这么做:
<transition name="fade">
<p v-if="kisses < 2" key="frog">? frog</p>
<p v-else-if="kisses >= 2 && kisses <=5" key="princess">? princess</p>
<p v-else key="santa">? santa</p>
</transition>
更好的方法是我们可以根据已有的数据动态的处理多元素过渡。
new Vue({
el: '#app-6',
data: {
kisses: 0,
kindOfTransformation: 'fade',
transformationMode: 'in-out'
},
computed: {
transformation() {
if (this.kisses < 3) {
return 'frog'
}
if (this.kisses >= 3 && this.kisses <= 5) {
return 'princess'
}
if (this.kisses > 5) {
this.kindOfTransformation = 'zoom'
this.transformationMode = 'out-in'
return 'santa'
}
},
emoji() {
switch (this.transformation) {
case 'frog':
return '?'
case 'princess':
return '?'
case 'santa':
return '?'
}
}
}
})
<div id="app-6">
<button @click="kisses++"><span>?</span>Kiss!</button>
<transition :name="kindOfTransformation" :mode="transformationMode">
<p :key="transformation">{{emoji}} {{transformation}}</p>
</transition>
</div>
以上过渡的name
、mode
以及元素的key
和内容,都将根据实例数据与计算属性进行动态的绑定。
这样做更加的灵活,并且可以为不同的元素应用不同的过渡(name
和mode
的不同)。
这里的mode
有三种情况:
- 不设置 旧元素离开过渡效果和新元素的进入过渡效果同时发生
- in-out 新元素进入过渡先进行,完毕后旧元素的离开过渡效果再发生 如下面公主先进入,青蛙后消失
- out-in 旧元素离开过渡先进行,完毕后新元素的进入过渡效果再发生 如下面公主先消失,圣诞老人后进入
再来看一个过渡模式的例子。
new Vue({
el: '#app-7',
data: {
product: 0,
products: ['?umbrella', '?computer', '?ball', '?camera']
}
})
#app-7 {
margin-left:300px;
}
#app-7 p {
position: absolute;
margin: 0;
font-size: 3em;
}
.slide-enter-active {
transition: transform .5s
}
.slide-leave-active {
transition: transform .5s;
transform: translateX(-300px);
}
.slide-enter {
transform: translateX(300px)
}
<div id="app-7" style="margin-bottom:100px;">
<button @click="product++">next</button>
<transition name="slide" >
<p :key="products[product % 4]">{{products[product % 4]}}</p>
</transition>
</div>
这似乎没什么问题。那么现在修改下CSS,去掉绝对定位。
#app-7 p {
/* position: absolute; */
margin: 0;
font-size: 3em;
}
再来看看结果。
似乎是有点不对劲,为什么会这样呢?
看看过渡执行时的DOM,发现前后两个元素的过渡是同时进行,这是Vue的默认情况,即两个<p></p>
同时存在,如果不使用绝对定位,那么上一个就会把下一个的位置挤掉。
这下过渡模式mode
就派上用处了,我们为过渡添加mode
属性为out-in
,旧先出新后进。
<transition name="slide" mode="out-in">
<p :key="products[product % 4]">{{products[product % 4]}}</p>
</transition>
列表过渡
列表过渡的情况比较复杂。一个问题一个问题看吧。
当<transition>
中有多个并列的元素时,我们又没有使用v-if|else
指令作用其上时,会警告我们使用<transition-group>
标签代替它。
一组列表的过渡效果,由<transition-group>
包围,有几点比单元素过渡特殊的。
- 在
<transition-group>
上设置tag
属性外包多个元素,如<transition-group tag="p">
- 在内部以
v-for
把元素渲染成列表形式 - 每个内部的元素需要使用
:key="data"
标记以与它的同胞们作区分 - 过渡的状态不止于
进入/离开
,多了一个移动,使用name-move
来定义类名(后面详细解释)。
看个普通示例。
new Vue({
el: '#app-1',
data: {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
},
methods: {
ramdomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.ramdomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.ramdomIndex(), 1)
}
}
})
.num-list-item {
margin-right: 10px;
}
.num-list-enter,
.num-list-leave-to {
opacity: 0;
transform: translateY(30px);
}
.num-list-enter-active,
.num-list-leave-active {
transition: all 2s;
}
<div id="app-1">
<button @click="add">添加</button>
<button @click="remove">移除</button>
<transition-group name="num-list" tag="p">
<span v-for="item in items" :key="item" class="num-list-item">
{{item}}
</span>
</transition-group>
</div>
很明显只有透明度的过渡,而Y轴(30px)的转换过渡没有成功,这是因为<span>
为一个inline
元素,没有这种转换过渡的功能,因此我们需要把它切换成inline-block
元素。
.num-list-item {
/*把span切换成inline-block*/
display:inline-block;
margin-right: 10px;
}
仔细观察下还能发现个问题,其他元素因为进入/离开
的那个元素,会发生位置的变化,其他元素这种移动变换没有过渡的效果,这不是我们想要的。
现在就轮到使用name-move
为其他元素在此时添加移动过渡效果,很简单的做个修改:
.num-list-move,/* 为其他受到影响的元素添加移动过渡效果*/
.num-list-enter-active,
.num-list-leave-active {
transition: all 2s;
}
观察文档结构,当进入/离开发生时,会给受影响的元素添加移动过渡类名num-list-move
。
进入过渡时没问题了,其他元素平滑的向右位移,但是离开过渡时其他受影响元素的移动还是没有过渡效果,这是因为定位问题,离开的元素要有2秒消失,默认的static
定位要在2秒后离开元素才会腾出空位让给后面的元素,而此时此刻,移动过渡的时效2秒已经过去了,因此后面元素才会很突兀的补位。
那么,我们可以在离开状态name-leave-active
上使用absolute
让元素脱离正常的文档流,那么一发生离开,后面的元素就可以开始正常的移动过渡了。如下修改:
/* 在此离开过渡的状态类名添加absolute定位,以受影响元素正常的使用平滑过渡 */
.num-list-leave-active {
position: absolute;
}
总结下,就是当使用行内元素时,使用位置转换的过渡需要把其设置为inline-block
,否则位置转换没有效果。在离开/进入过渡时,受影响的其他元素应该使用移动过渡name-move
为期定义移动过渡。还需要在离开过渡状态类中name-leave-active
设置离开过渡元素的定位为absolute
使其脱离正常的文档流,以不妨碍其他元素的移动过渡。
列表过渡的另一种写法
观察其上CSS代码,可以发现<span>
的进入/离开/移动过渡定义的过渡都一样,即:
.num-list-move,
.num-list-enter-active,
.num-list-leave-active {
transition: all 2s;
}
因此可以去掉这些过渡状态类名,之间写在<span>
的样式里就可以了。
.num-list-item {
display:inline-block;
margin-right: 10px;
/* 代替 进入/离开/移动过渡状态类 */
transition: all 2s;
}
.num-list-enter,
.num-list-leave-to {
opacity: 0;
transform: translateY(30px);
}
/*
.num-list-move,
.num-list-enter-active,
.num-list-leave-active {
transition: all 2s;
}
*/
/* 在此离开过渡的状态类名添加absolute定位,以受影响元素正常的使用平滑过渡 */
.num-list-leave-active {
position: absolute;
}
其他都原封不动,效果和第一种一样。
为移动过渡添加功能
在第一种写法的基础上添加以下功能,再次理解移动过渡。
js中导入lodash库,使用shuffle
方法重新排列数组items
。
methods: {
ramdomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.ramdomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.ramdomIndex(), 1)
},
//使用`shuffle`方法重新排列数组`items`。
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
页面添加一个按钮
<div id="app-1">
<button @click="add">添加</button>
<button @click="remove">移除</button>
<button @click="shuffle">重排列</button>
<transition-group name="num-list" tag="p">
<span v-for="item in items" :key="item" class="num-list
-item">
{{item}}
</span>
</transition-group>
</div>
列表过渡的其他示例
一个汽车站:
new Vue({
el: '#app-2',
data: {
buses: [1, 2, 3, 4, 5],
nextBus: 6
},
mounted() {
setInterval(() => {
const headOrTail = () => Math.random() >= 0.5
if (headOrTail()) {
this.buses.push(this.nextBus)
this.nextBus += 1
} else {
this.buses.splice(0, 1)
}
}, 2000)
}
})
.station-bus {
display: inline-block;
margin-left: 10px;
font-size: 2em;
}
.station-enter {
opacity: 0;
transform: translateX(30px);
}
.station-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.station-move,
.station-enter-active,
.station-leave-active {
transition: all 2s;
}
.station-leave-active {
position: absolute;
}
<div id="app-2">
<h3>公交车站</h3>
<transition-group tag="p" name="station">
<span v-for="bus in buses" :key="bus" class="station-bus">?</span>
</transition-group>
{{buses}}
</div>
在元素插入时的钩子上定义一个timer
,每隔两秒一辆车进入或离开,为它们设置进入/离开过渡,和其他受影响车辆的移动过渡。
使用组件包装可重用的过渡
如果想要在我们的站点的各处重用一种过渡,把它包装进一个组件是个好方法。
要在站点上展示/隐藏一些缩略的文章,我们可以编写一个过渡组件,然后为不同的缩略文章添加这个过渡组件使其拥有过渡效果。
Vue.component('puff', {
functional: true,
render: function (createElement, context) {
var data = {
props: {
'enter-active-class': 'magictime puffIn',
'leave-active-class': 'magictime puffOut'
}
}
return createElement('transition', data, context.children)
}
})
new Vue({
el: '#app-3',
data: {
showRecipe: false,
showNews: false
}
})
<link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/magic/1.1.0/magic.min.css">
<div id="app-3">
<button @click="showRecipe = !showRecipe">
Recipe
</button>
<button @click="showNews= !showNews">
Breaking News
</button>
<puff>
<article v-if="showRecipe" class="card">
<h3>
过渡和动画
</h3>
<p>
自定义过渡的类名 在钩子中使用Velocity 两个元素之间的过渡 ...
</p>
</article>
</puff>
<puff>
<article v-if="showNews" class="card">
<h3>
今日头条
</h3>
<p>
2017、2018面试分享(js面试题记录)记得点赞分享哦;让更多的人看到~~
</p>
</article>
</puff>
</div>
这里使用了一个magicCSS动画库和函数式组件(todo)。
定义一个全局的函数式组件。在其render
选项中定义函数并返回一个可重用的元素<puff>
,在内部通过magic将进入/离开的过渡效果添加到<puff>
的属性上。在页面需要的地方包裹该<puff>
元素即可。
动态过渡
响应是Vue永恒的主体,因此过渡和它的属性都可以是动态的。这样我们可以控制在特定的位置与时间使用特定的过渡。
在多个元素之间的过渡和过渡模式中我们已经展示了动态过渡,对于不同的<p>
元素,我们使用了不同的过渡效果和模式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。