一、Vue中的动画实现原理
Vue实际上是通过动态地给< transition >
标签包裹的内容添加class,然后结合css对不同的class实现不同样式,最终实现动画效果
二、Vue过渡的实现方式
- 单个元素
<div id="box">
<button @click="isShow=!isShow">click</button>
<transition name="fade">
<div v-show="isShow">Vue1</div>
</transition>
</div>
.fade-enter-active,.fade-leave-active{
transition: all 1.5s;
}
.fade-enter,.fade-leave-to{
opacity: 0;
transform: translateX(100px);
}
var vm = new Vue({
el:"#box",
data:{
isShow:false
}
})
< transition >若没有设置name属性,那么默认为v-enter、v-enter-active和v-leave、v-leave-to
Vue会帮我们在合适的时机使用它们。比如在实现淡入时:
第一帧动画,会自动添加fade-enter和fade-enter-active类
第二帧动画,会去掉fade-enter类,添加fade-enter-to类
第三帧动画,会去掉所有enter类
淡出也是如此。
- Vue使用animate.css动画库
我们知道在过渡类名中v-enter-active和v-enter-active这两个class在元素执行过渡或动画的过程中一直存在。所以我们可以省略除了v-enter-active和v-enter-active外其他class,使用关键帧@keyframes来控制元素动画的初始和结束状态。
首先我们可以自定义过渡类名mybounce:
<div id="box">
<transition name="mybounce">
<div v-show="isShow">Vue2</div>
</transition>
</div>
.mybounce-enter-active{
animation: bounce-in .5s;
}
.mybounce-leave-active{
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0%{
opacity: 0;
transform: translateX(100px);
}
100%{
opacity: 1;
transform: translateX(0px);
}
}
既然我们可以使用自定义class,那么我们就可以使用开源的第三方CSS库,比如animate.css库。
使用很简单,直接替换上面我们自定义的class就行。
<div id="box">
<h1 class="animate__animated animate__bounce">animated</h1>
<h2 class="animate__animated animate__pulse">animated</h2>
</div>
将animate__animated以及任何动画名称添加到元素中即可。(不要忘记animate__前缀!)
3.Vue中同时使用过渡和动画
先简单的用两个animated动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<title>Title</title>
</head>
<body>
<div id="app">
<transition
enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__bounce"
>
<h1 v-if="show">Hello</h1>
</transition>
<button @click="handleBtnClick">{{btnText}}</button>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
show: true,
btnText: '显示',
},
methods: {
handleBtnClick: function(){
this.show = !this.show;
this.btnText = this.btnText === '显示'? '隐藏' : '显示';
},
}
});
</script>
</body>
</html>
我们点击change的时候特效是正常的,但是当我们重新刷新页面的时候会发现第一次渲染的时候,并没有效果出来,那么如果我现在想要页面刷新的时候就有特效出来该怎么做呢?
<transition
appear
appear-active-class="animate__animated animate__bounce"
enter-active-class="animate__animated animate__bounce"
leave-active-class="animate__animated animate__bounce"
>
<h1 v-if="show">Hello</h1>
</transition>
apper表示页面第一次渲染出来就需要显示效果,效果的内容是appear-active-class
保存刷新,发现直接就有特效出来了。
我们现在用的antimate动画其实是用keyframes写的css效果,但是如果我们想要transiton的过渡效果和antimate动画效果一起使用怎么办呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style type="text/css">
.fade-enter{
opacity: 0;
}
.fade-enter-active{
transition: opacity 3s;
}
.fade-leave-to{
opacity: 0;
}
.fade-leave-active{
transition: opacity 3s;
}
</style>
</head>
<body>
<div id="vm">
<transition
name="fade"
enter-active-class="animate__animated animate__bounce fade-enter-active"
leave-active-class="animate__animated animate__bounce fade-enter-active">
<div v-if="show">Hello World</div>
</transition>
<button @click="handleClick" type="button">change</button>
</div>
<script>
var vm = new Vue({
el: '#vm',
data: {
show: true
},
methods: {
handleClick: function() {
this.show = !this.show;
}
}
});
</script>
</body>
</html>
现在既有keyframes这种CSS动画,又有transiton这种过渡动画,
我们自己设置的transtion这种动画是3s,那么animate这种动画效果,查看源码可以看到是1s。
那么,整个动画是以什么时长为结束时长呢?Vue自己也搞不清楚以哪个为结束时长,所以这个时候我们可以手动设置transition的type属性。
type="transition"则以过渡的时长为准,type="animation"以动画时长为准。
我们还可以利用:duration来自定义时长。
<transition
name="fade"
:duration:"2000"
enter-active-class="animate__animated animate__bounce fade-enter-active"
leave-active-class="animate__animated animate__bounce fade-enter-active">
<div v-if="show">Hello World</div>
</transition>
还可以分别定义入场和出场动画时长。
<transition
name="fade"
:duration="{enter:2000,leave:5000}"
enter-active-class="animate__animated animate__bounce fade-enter-active"
leave-active-class="animate__animated animate__bounce fade-enter-active">
<div v-if="show">Hello World</div>
</transition>
- JS钩子函数
js动画是通过绑定在上的钩子函数完成的。
<transition
@before-appear 都是第一次打开页面(刷新)
@appear
@after-appear
@before-enter="beforeEnter" 进入动画前执行
@enter="enter" 进入动画时执行
@after-enter="afterEnter" 进入动画完成后,在enter中调用done()告诉vue,js动画结束才会触发
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
>
</transition>
在定义的方法中都会接收到一个参数
methods: {
beforeEnter: function(el){ //这里接收到的el就是transition包裹这的dom元素
el.style.color='red';
}
enter: function(el,done){
done(); 只有调用这个回调函数,Vue才知道动画执行结束
}
afterEnter: function(el){
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>钩子函数与动画</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<p v-if="show">
Demo
</p>
</transition>
</div>
<script>
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})
</script>
</body>
</html>
可以在 attribute 中声明 JavaScript 钩子,这些钩子函数可以结合transitions/animations 使用,也可以单独使用。
注意:(1)推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css=“false”,Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。(2)当只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
- 多个元素的过渡
<div id="box">
<button @click="isShow=!isShow">click</button>
<transition name="mybounce" mode="out-in">
<p v-if="isShow" key="1" >Vue1</p>
<p v-else key="2">Vue2</p>
</transition>
</div>-->
.mybounce-enter-active{
animation: bounce-in .5s;
}
.mybounce-leave-active{
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0%{
opacity: 0;
transform: translateX(100px);
}
100%{
opacity: 1;
transform: translateX(0px);
}
}
由于因Vue的复用机制,若不指定key值,Vue不会对dom进行创建和删除操作而会直接复用,所以动画不会执行。可给元素添加唯一key值来使vue对该元素不进行复用。
- 多个组件的过渡
多个组件的过渡动画注意的就只有一点:在动态组件外层包裹一个transition就行了,模式使用mode去控制。
.v-enter , .v-leave-to {
opacity: 0;
}
.v-enter-active , .v-leave-active {
transition: opacity 2s;
}
<div id="box">
<transition mode="in-out">
<component :is="type"></component>
</transition>
<button @click="handleClick">toggle</button>
</div>
Vue.component('child',{
template:'<div>child</div>'
});
Vue.component('child-one',{
template:'<div>child-one</div>'
})
var vm = new Vue({
el: "#box",
data: {
type: 'child',
},
methods: {
handleClick: function() {
this.type = this.type === 'child'?'child-one':'child';
}
}
})
- 列表过渡
当我们希望对列表进行过渡效果时,使用transition-group标签就可以了。
(1)不同于 < transition >,它会以一个真实元素呈现:默认为一个 < span >。也可以通过 tag属性更换为其他元素。
(2)过渡模式(mode)不可用
(3)内部元素总是需要提供唯一的 key attribute 值。
(4)CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>列表过渡</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.fade-enter,
.fade-leave-to{
opacity: 0;
}
.fade-enter-active,
.fade-leave-active{
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="box">
<transition-group name="fade">
<div v-for="item of list" :key="item.id">{{item.title}}-----{{item.id}}</div>
</transition-group>
<button @click="handleClick">添加</button>
</div>
<script>
let vm = new Vue({
el: '#box',
data: {
count:0,
list:[]
},
methods: {
handleClick() {
this.list.push({
id:this.count ++,
title:'hello world'
})
}
}
})
</script>
</body>
</html>
transition-group标签相当于在每一个< span >标签外层都加了一个transition标签,把列表的过渡转化为单个元素的过渡。