Vue动画

Posted by Zxd on March 20, 2018

单元素/组件的过渡

在以下情形中,transition封装组件,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染(v-if)
  • 条件展示(v-show)
  • 动态组件
  • 组件根节点?

过渡的类名

  1. v-enter
  2. v-enter-active
  3. v-enter-to
  4. v-leave
  5. v-leave-active
  6. v-leave-to
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="root">
<!-- transition会给内部元素(如div)加上fade-enter等类名-->
<!-- fade-enter在动画第二帧就移除,fade-enter-active在最后一帧移除 -->
<!-- 这里name不写fade,css中就默认以v-enter开头 -->
<transition name="fade">
<!-- v-show v-if 动态组件都可以 -->
<div v-if="show">hello world</div>
</transition>

<button @click="handleClick">切换</button>
</div>
<script>
let vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.fade-enter, .fade-leave-to{
opacity: 0;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1s;
}

/*如果transition不加name属性 默认以v-enter等开头*/
.v-enter,
.v-leave-to{
opacity: 0;
}
.v-enter-active,
.v-leave-active{
transition: opacity 1s;
}

css过渡

1
2
3
4
5
6
7
8
<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
1
2
3
4
5
6
new Vue({
el: '#example-1',
data: {
show: true
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}

css动画

  • @keyframes定义一个动画名字,然后在过渡类名中animation: xxx 1s;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@keyframes bounce-in{
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
.fade-enter-active{
transform-origin: left center;
animation: bounce-in 1s;
}
.fade-leave-active{
transform-origin: left center;
animation: bounce-in 1s reverse;
}

/*另外可以自定义类名*/
.active{
transform-origin: left center;
animation: bounce-in 1s;
}
.leave{
transform-origin: left center;
animation: bounce-in 1s reverse;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<link rel="stylesheet" href="./animate.css">

<div id="root">
<transition name="fade">
<!-- v-show v-if 动态组件都可以 -->
<div v-if="show">hello world</div>
</transition>
<br>
<transition
enter-active-class="active"
leave-active-class="leave"
>
<!-- 可以自定义transition的class类名 -->
<div v-if="show">哈哈啊哈哈</div>
</transition>
<br>
<!-- 使用animate.css动画库 引入了css后不需要再写css-->
<transition
enter-active-class="animated swing"
leave-active-class="animated shake"
>
<div v-if="show">哇哦哇哦</div>
</transition>

<button @click="handleClick">切换</button>
</div>
<script>
let vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>

同时使用过渡和动画

  • 当同时使用二者时,在transition组件中设置type=transition或animation来声明Vue需要监听的类型
  • :duration="1000"定制一个显性的过渡持续时间,以毫秒计
  • appear表示设置节点初始渲染时的过渡,配合appear-active-class等使用,也可以使用自定义类名custom-appear-to-class
1
2
3
4
5
6
7
8
.fade-enter,
.fade-leave-to{
opacity: 0;
}
.fade-enter-active,
.fade-leave-active{
transition: opacity 3s
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- animate.css是@keyframes类型 -->
<link rel="stylesheet" href="./animate.css">

<!-- animate.css 的动画使用的是@keyframes的类型 -->
<div id="root">
<!-- 使用animate.css动画库 引入了css后不需要再写css-->
<!-- 设置type=transition表示以transition的时长为准 -->
<!-- 或者设置自定义时长 :duration="10000" 10s -->
<!-- :duration = "{enter:5000,leave:10000}" -->
<!-- appear表示设置节点初始渲染时的过渡 -->
<transition
name="fade"
type="transition"
:duration = '5000'
appear
enter-active-class="animated swing fade-enter-active"
leave-active-class="animated shake fade-leave-active"
appear-active-class="animated swing"
>
<!-- 可以自定义transition的class类名 -->
<div v-if="show">哈哈啊哈哈</div>
</transition>

<button @click="handleClick">切换</button>
</div>
<script>
let vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})
</script>

JS动画

JavaScript钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
  • 这些钩子函数可以结合CSStransition/animation一起使用
  • 当只用JavaScript过渡的时候,enterleave中必须使用done进行回调,否则,它们将会被同步调用,过渡会立即完成
  • 推荐对当仅使用js过渡的元素添加v-bind:css="false",Vue会跳过css检测,避免过渡过程中css影响

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script src="velocity.js"></script>

<div id="root">
<transition
name="fade"
@before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"

@before-leave
@leave
@after-leve
>
<div v-show="show">Hello world</div>
</transition>

<button @click="handleClick">toggle</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let vm = new Vue({
el: '#root',
data: {
show: true
},
methods: {
handleClick() {
this.show = !this.show
},
handleBeforeEnter(el){
el.style.opacity = 0;
// el.style.color = 'red'
// console.log('before enter')
},
handleEnter(el,done){
Velocity(el,{opacity:1},{duration:1000,complete:done})
// setTimeout(()=>{
// el.style.color = 'green'
// },2000)
// setTimeout(() => {
// done() // 标志动画执行完
// }, 4000);
},
handleAfterEnter(el){
// el.style.color = 'black'
console.log('动画结束')
}
}
})

多个元素或组件的过渡

  • 当具有相同标签名的元素进行切换时,需要通过key设置唯一标记
  • <transition>组件中的多个元素设置key是一个更好的实践时

过渡模式

  • in-out不常用:新元素先进行过渡,完成后当前元素过渡离开
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入

多个组件的过渡

  • 不需要使用key特性,只需要使用动态组件-v-bind:is="type"
1
2
3
4
5
6
7
8
.v-enter,
.v-leave-to{
opacity: 0;
}
.v-enter-active,
.v-leave-active{
transition: opacity 1s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="root">
<transition mode="in-out">
<!-- 加上key值 让vue不复用dom才可以实现多个元素之间的动画 -->
<div v-if="show" key="hello">Hello world</div>
<div v-else key="bye">Bye world</div>
</transition>

<br><br>
<transition mode="out-in">
<child-one v-if="show"></child-one>
<child-two v-else></child-two>
</transition>

<br><br>
<transition mode="out-in">
<component :is="type"></component>
</transition>

<button @click="handleClick">toggle</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Vue.component('child-one',{
template:'<div>Child-one</div>'
})

Vue.component('child-two',{
template:'<div>Child-two</div>'
})

let vm = new Vue({
el: '#root',
data: {
show: true,
type:'child-one'
},
methods: {
handleClick() {
this.show = !this.show
this.type = this.type==='child-one'?'child-two':'child-one'
}
}
})

列表过渡

<transition-group>列表过渡

  • 过渡模式不可用,如mode="in-out",因为我们不再相互切换特有的元素
  • 内部元素总是需要提供唯一的key属性值
1
2
3
4
5
6
.v-enter,.v-leave-to{
opacity: 0;
}
.v-enter-active,.v-leave-active{
transition: opacity 1s;
}
1
2
3
4
5
6
7
8
<div id="root">
<transition-group>
<div v-for="(item,index) of list" :key="item.id">
{{item.title}}
</div>
</transition-group>
<button @click="handleClick">Add</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let count = 0;
let vm = new Vue({
el: '#root',
data: {
list: []
},
methods: {
handleClick() {
this.list.push({
id: count++,
title:'hello world'
})
}
}
})

动画封装

1
2
3
4
5
6
7
8
9
10
11
<div id="root">
<!-- 父组件向子组件传值,传的值是后一个show,值为true,前面是子组件props要接收的名字-->
<fade :show="show">
<div>hello world</div>
</fade>

<fade :show="show">
<h2>哈哈哈啊哈</h2>
</fade>
<button @click="handleClick">toggle</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Vue.component('fade',{
props:['show'],
// 这里用v-show不行
template:`
<transition @before-enter="handleBeforeEnter"
@enter="handleEnter">
<slot v-if="show"></slot>
</transition>
`,
methods:{
handleBeforeEnter(el){
el.style.color = 'red'
},
// @enter这个函数有两个参数,记住手动结束done
handleEnter(el,done){
setTimeout(() => {
el.style.color
done()
}, 2000);
}
}
})

let vm = new Vue({
el: '#root',
data: {
show:true
},
methods: {
handleClick() {
this.show = !this.show
}
}
})