第5章 Vue 中的动画特效

本章将讲解 Vue 中的 CssJs 动画原理,以及在 Vue 中如使用 Animate.cssVelocity.js 动画库,在理解了基础动画原理后,本章还扩展了 Vue 中多元素及列表过渡效果实现的知识,并会带同学们学习如何对通用动画效果进行代码封装。

5-1 Vue 动画 - VueCSS 动画原理

添加动画的标签使用 transition 标签包裹

通过某一时刻向标签上 增加/去除 class 来实现

叫做 css 动画效果 / 过渡动画效果

隐藏 -> 显示

构建一个隐藏的动画流畅

动画执行的原理

  • 即将执行的瞬间前会在元素上增加两个calss名字
    • fade-enter
    • fade-enter-active
  • 当第一帧执行结束,运行第二帧的时候执行
    • 去除 fade-enter
    • 新增 fade-enter-to
  • 执行到最后一帧的时候
    • 去除 fade-enter-active
    • 去除 fade-enter-to

因为代码中 name="fade" 所以,class 名字为 fade-
如果不写 name 的话,class 名字为 v-

显示 -> 隐藏

构建一个隐藏的动画流畅

动画执行的原理

  • 即将执行的瞬间前会在元素上增加两个calss名字
    • fade-leave
    • fade-leave-active
  • 当第一帧执行结束,运行第二帧的时候执行
    • 去除 fade-leave
    • 新增 fade-leave-to
  • 执行到最后一帧的时候
    • 去除 fade-leave-active
    • 去除 fade-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
25
26
27
28
29
30
31
32
33
34
35
<style>
.fade-leave-to,
.fade-enter {
opacity: 0;
}

.fade-leave-active,
.fade-enter-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<transition name="fade">
<div v-show="show">v-show</div>
</transition>
<transition name="fade">
<div v-if="show">v-if</div>
</transition>
<button @click="handleBtnClick">切换</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show:true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
}
},
})
</script>

课下阅读官方文档 进入/离开 & 列表过渡 章节内容


Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:

  • 在 CSS 过渡和动画中自动应用 class
  • 可以配合使用第三方 CSS 动画库,如 Animate.css
  • 在过渡钩子函数中使用 JavaScript 直接操作 DOM
  • 可以配合使用第三方 JavaScript 动画库,如 Velocity.js

在这里,我们只会讲到进入、离开和列表的过渡,你也可以看下一节的管理过渡状态


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

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

当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:

  1. 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。

  2. 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。

  3. 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)


在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

  3. v-enter-to2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

  4. v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

  6. v-leave-to2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

过渡的类名

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter 会替换为 my-transition-enter

v-enter-activev-leave-active 可以控制进入/离开过渡的不同的缓和曲线,在下面章节会有个示例说明。


5-2 在Vue 中使用 animate.css

动画库使用

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
40
41
42
<style>
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}

.fade-leave-active {
transform-origin: left center;
animation: bounce-in 1s reverse;
}

.fade-enter-active {
transform-origin: left center;
animation: bounce-in 1s;
}
</style>
<div id="app">
<transition name="fade">
<div v-show="show">v-show</div>
</transition>
<button @click="handleBtnClick">Toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show:true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
}
},
})
</script>

修改默认命名方式

通过给 transition 添加 enter-active-classleave-enter-class 实现自定义命名

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
40
41
42
43
44
45
<style>
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}

.leave {
animation: bounce-in 1s reverse;
}

.active {
animation: bounce-in 1s;
}
</style>
</head>
<body>
<div id="app">
<transition
enter-active-class="active"
leave-active-class="leave"
>
<div v-show="show">v-show</div>
</transition>
<button @click="handleBtnClick">Toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
show:true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
}
},
})
</script>

使用 animate.css

  • 必须自定义 class 名字来使用 animate.css
  • class 类里面必须包含 animated 类,同时根据情况添加需要的动画效果的 class

5-3 在Vue 中同时使用过渡和动画

打开既执行动画

增加 appear

1
2
3
4
5
6
<transition
appear
appear-active-class="animated swing"
>
<!-- 内容 -->
</transition>
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
<div id="app">
<transition
appear
enter-active-class="animated shake" // 元素显示执行
leave-active-class="animated tada" // 元素隐藏执行
appear-active-class="animated swing" // 打开页面就执行
>
<div v-show="show">
{{mess}}
</div>
</transition>
<button @click="handleBtnClick">toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
}
},
})
</script>

增加过渡效果

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
40
41
42
43
<style>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
@keyframes identifier {

}
.fade-enter-active,
.fade-leave-active {
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<transition
type="transition"
appear
enter-active-class="animated shake fade-enter-active"
leave-active-class="animated tada fade-leave-active"
appear-active-class="animated swing"
>
<div v-show="show">
{{mess}}
</div>
</transition>
<button @click="handleBtnClick">toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
}
},
})
</script>
  • 使用 transition 过渡动画为总时间
    • type="transition"
  • 使用 @keyframes animated 动画时间为总时间
    • ??????????????????
  • 自定义动画播放时间(例如:总时长为2s)
    • :duration = "2000"
  • 复杂自定义动画播放时间(动画开始/结束时间不一样)
    • :duratione="{center:5000,leave:1000}"

5-4 Vue 中的 Js 动画与 Velocity.js 的结合

Velocity.js 官网:http://www.velocityjs.org

JS动画钩子

进场钩子

  • before-enter
  • enter
  • after-enter

出场钩子

  • before-leave
  • leave
  • after-leave

下面为进场动画案例,出场动画与此类似

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
40
41
42
43
44
45
<div id="app">
<transition
@before-enter="handleBeforeClick"
@enter="handlEnterClick"
@after-enter="handlAfterClick"
>
<div v-show="show">
{{mess}}
</div>
</transition>
<button @click="handleBtnClick">toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
},
// 显示/入场 的时候执行,一个参数
handleBeforeClick: function(el) {
el.style.color="red"
console.log("handleBeforeClick",el)
},
// 显示/入场 的时候执行,两个参数el,done(回调函数)
handlEnterClick: function(el,done){
setTimeout(()=>{
el.style.color="blue"
// 要调用一下done回调函数,证明动画已经结束了
},1000)
setTimeout(() => {
done()
}, 3000);
console.log("handlEnterClick",el,done)
},
handlAfterClick: function(el){
el.style.color="pink"
}
},
})
</script>

Velocity.js

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
40
41
42
43
<div id="app">
<transition
@before-enter="handleBeforeClick"
@enter="handlEnterClick"
@after-enter="handlAfterClick"
>
<div v-show="show">
{{mess}}
</div>
</transition>
<button @click="handleBtnClick">toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleBtnClick: function(){
this.show = !this.show
},
// 显示/入场 的时候执行,一个参数
handleBeforeClick: function(el) {
el.style.opacity = 0
},
// 显示/入场 的时候执行,两个参数el,done(回调函数)
handlEnterClick: function(el,done){
Velocity(el,
{
opacity:1
},{
duration:1000,
complete: done
})
},
handlAfterClick: function(el){
alert("动画结束")
}
},
})
</script>

5-5 Vue 中多个元素或组件的过渡

多个元素的过渡

  • 不使用key的话节点直接复用了,不存在动画效果了,使用 key 解决复用dom的情况,动画恢复
  • mode="in-out" 先进入在隐藏
  • mode="out-in" 先隐藏在进入
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
40
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}

.v-enter-active,
.v-leave-active{
transition: opacity 1s;
}
</style>
</head>
<body>
<div id="app">
<!--
mode="in-out" 先进入在隐藏
mode="out-in" 先隐藏在进入
-->
<transition mode="out-in">
<!-- 不使用key的话节点直接复用了,不存在动画效果了,使用 key 解决复用dom的情况,动画恢复 -->
<div v-if="show" key="hello">{{mess1}}</div>
<div v-else key="bye">{{mess2}}</div>
</transition>
<button @click="handleClick">toggle</button>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
mess1:"Hello World",
mess2:"Bye world",
show: true
},
methods: {
handleClick: function(){
this.show = !this.show
}
},
})
</script>

多个组件的过渡 / 动态组件

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
40
41
42
43
44
45
46
47
48
49
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}

.v-enter-active,
.v-leave-active{
transition: opacity 1s;
}
</style>
<div id="app">
<!--
in-out 先进入在隐藏
out-in 先隐藏在进入
-->
<transition mode="out-in">
<!-- 动态组件 -->
<component :is="type"></component>
<!--
<child v-if="show">{{mess1}}</child>
<child-one v-else>{{mess2}}</child-one>
-->
</transition>
<button @click="handleClick">toggle</button>
</div>
<script>
Vue.component('child',{
template: '<div>child</div>'
})

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

var app = new Vue({
el: "#app",
data: {
mess1:"Hello World",
mess2:"Bye world",
type: 'child'
},
methods: {
handleClick: function(){
this.type = ( this.type === 'child' ? 'child-one' : 'child' )
}
},
})
</script>

5-6 Vue 中的列表过渡

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
40
41
42
43
44
45
46
47
48
<style>
.v-enter,
.v-leave-to {
opacity: 0;
}

.v-enter-active,
.v-leave-active {
transition: opacity 1s;
color: #48b785;
}
</style>
<div id="app">
<transition-group>
<div v-for="(item,index) of list" :key="item.id">
{{item.title}}
</div>
</transition-group>
<!--
<transition>
<div>hello world</div>
</transition>
<transition>
<div>hello world</div>
</transition>
<transition>
<div>hello world</div>
</transition>
-->
<button @click="handleBtnClick">Add</button>
</div>
<script>
var count = 0;
var app = new Vue({
el: "#app",
data: {
list:[]
},
methods: {
handleBtnClick: function(){
this.list.push({
id: count++,
title: 'hello world'+ count
})
}
},
})
</script>

5-7 Vue 中的动画封装

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
40
41
42
<style>
.v-enter ,
.v-leave-to {
opacity: 0;
}

.v-enter-active,
.v-leave-active {
transition: opacity 1s;
}
</style>
<div id="app">
<fade :show="show">
<div>{{mess}}</div>
</fade>
<fade :show="show">
<h1>{{mess}}</h1>
</fade>
<button @click="handleClick">toggle</button>
</div>
<script>
Vue.component('fade', {
props: ['show'],
template: `
<transition>
<slot v-if="show"></slot>
</transition>
`
})
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleClick: function(){
this.show = !this.show
}
},
})
</script>

全部封装在组件中

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
40
41
42
43
44
45
<div id="app">
<fade :show="show">
<div>{{mess}}</div>
</fade>
<fade :show="show">
<h1>{{mess}}</h1>
</fade>
<button @click="handleClick">toggle</button>
</div>
<script>
// 完整的动画全部封装在组件中
Vue.component('fade', {
props: ['show'],
template: `
<transition @before-enter="handleBeforeEnter"
@enter="handleEnter"
>
<slot v-if="show"></slot>
</transition>
`,
methods: {
handleBeforeEnter: function(el){
el.style.color = "red"
},
handleEnter: function(el,down){
setTimeout(() => {
el.style.color = "blue"
down()
}, 1000);
}
}
})
var app = new Vue({
el: "#app",
data: {
mess:"hello world",
show: true
},
methods: {
handleClick: function(){
this.show = !this.show
}
},
})
</script>

5-8 本章小节

  • Vue 过渡动画
  • keyframes 动画
  • 通过 js 实现动画
  • animate.cssVelocity.js 动画库
  • 多个元素切换动画
  • 列表动画

课下阅读官方文档 状态过渡 章节内容

5-9 【讨论题】前端动画是如何实现的?

  • transition 过渡动画
    • 常与 :hover, :active等伪类使用,实现相应等动画效果。
  • animation 关键帧动画
    • 比如:loading 展示,代码如上。
    • 优点:
      • 1、无需每一帧都被记录,通过关键帧设置,方便开发;
      • 2.实现简单,通常 UI 可以直接给到 css 文件,前端只需要导入即可【移动端注意屏幕适配】。
    • 缺点:
      • 1.css 没法动画交互,无法得知当前动画执行阶段;
      • 2.transition: 需要触发,无法自动播放;
      • 3.animation 兼容性需要加前缀,导致代码量成倍增长;
      • 4.对于复杂动画的实现,导入的 css 文件过大,影响页面的渲染树生成,从而阻塞渲染。比如实现一个摇钱树的效果,css 文件达到百 kb,就要采取一些必要的压缩手段,缩减文件大小。
  • js 逐帧动画
    • JS 动画的原理是通过 setTimeout 或 requestAnimationFrame 方法绘制动画帧,从而动态地改变 网页中图形的显示属性(如 DOM 样式,canvas 位图数据,SVG 对象属性等),进而达到动画的目的。
  • 使用canvas绘制动画
    • canvas作为H5新增元素,是借助Web API来实现动画的。
    • 优点:
      • 可以应对页面中多个动画元素渲染较慢的情况,完全通过javascript来渲染控制动画的执行。可用于实现较复杂动画。
  • SVG 动画
    • SVG是一种基于XML的图像格式,非常类似于HTML的工作方式。它为许多熟悉的几何形状定义了不同的元素,这些元素可以在标记中组合以产生二维图形。
    • 优点:
      • 优化 SEO 和无障碍的利器,因为 SVG 图像是使用XML(可扩展标记语言【英语:Extensible Markup Language,简称:XML】标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等)来标记构建的,浏览器通过绘制每个点和线来打印它们,而不是用预定义的像素填充某些空间。这确保 SVG 图像可以适应不同的屏幕大小和分辨率。
      • 由于是在 XML 中定义的,SVG 图像比 JPG 或 PNG 图像更灵活,而且我们可以使用 CSS 和 JavaScript 与它们进行交互。SVG 图像设置可以包含 CSS 和 JavaScript。在 react、vue 这种数据驱动视图的框架下,对于 SVG 操作就更加如鱼得水了。(下文会跟大家分享一些小的 SVG 动画在我们项目中的实践)
      • 在运用层面上,SVG 提供了一些图像编辑效果,比如屏蔽和剪裁、应用过滤器等等。并且 SVG 只是文本,因此可以使用 GZip 对其进行有效压缩。

第5章 Vue 中的动画特效
http://xiaodongxier.github.io/pages/4fb4efc0.html
作者
WangYongJie
发布于
2022年8月22日
更新于
2023年2月16日
许可协议