宋宇的it博客

vuePress-theme-reco 宋宇    2020 - 2021
宋宇的it博客 宋宇的it博客

Choose mode

  • dark
  • auto
  • light
首页
博客
  • diary
  • diyProgram
  • linux
  • mac
  • react
  • resources
  • tools
  • vue2.x
  • vue3
  • webpack5
时间轴
文档
  • indexdb
  • http
  • ts
  • vue3
  • vue2
  • angular
  • mongo
  • donate
更多
  • Gitee
  • Github
  • olDocs
author-avatar

宋宇

22

文章

10

标签

首页
博客
  • diary
  • diyProgram
  • linux
  • mac
  • react
  • resources
  • tools
  • vue2.x
  • vue3
  • webpack5
时间轴
文档
  • indexdb
  • http
  • ts
  • vue3
  • vue2
  • angular
  • mongo
  • donate
更多
  • Gitee
  • Github
  • olDocs
  • vue入门实例
  • 自定义指令
  • /docs/vue2docs/11.过渡与动画.html
  • 组件
  • 引入第三方库
  • 混入
  • axios请求
  • Mock模拟接口
  • vue-router
  • vuex状态管理
  • 模板语法
  • 条件渲染
  • 循环/列表渲染
  • 过滤器
  • 事件处理器
  • 计算属性
  • class与style绑定
  • 表单输入绑定

vuePress-theme-reco 宋宇    2020 - 2021

宋宇

https://cn.vuejs.org/v2/guide/transitions.html

# 过度与动画

# 一、过渡

# 1、概述

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

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

vue.js 通过 transition 组件实现过渡效果

# 2、过渡类名

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

提示:

  1. 假设 transition 设有 name 属性,class 名将 v- 替换为 name 属性值。(eg:name 为fade,对应的类名为 -> fade-enter、fade-enter-active依次类推)。

# 3、单元素/组件过渡

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

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

【实例 - 01】基础示例

<template>
    <div class="transition">
       <button type="button" @click='isVisible = !isVisible'>TOGGLE</button>
       <transition name="fade">
            <h1 v-show="isVisible">Hello, World!</h1>
       </transition>
    </div>
</template>

<script>
export default {
    name: "animate",
    data() {
        return {
            isVisible: false
        }
    }
}
</script>
<style scoped>

.fade-enter-active,
.fade-leave-active {
    transition: opacity 1s linear;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}

</style>
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

效果演示:

【实例 - 02】通过CSS实现过渡

<template>
    <div class="transition">
       <button type="button" @click='isVisible = !isVisible'>TOGGLE</button>
       <transition name="slide">
            <h1 v-show="isVisible">Hello, World!</h1>
       </transition>
    </div>
</template>

<script>
export default {
    name: "animate",
    data() {
        return {
            isVisible: true
        }
    }
}
</script>
<style scoped>

.slide-enter-active,
.slide-leave-active {
    transition: all 1s linear;
}
.slide-enter,
.slide-leave-to {
    opacity: 0;
    transform: translateX(100px)
}

</style>
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

效果演示:

【实例 - 03】通过动画实现过渡

动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。

<template>
    <div class="transition">
       <button type="button" @click='isVisible = !isVisible'>TOGGLE</button>
       <transition name="bounce">
            <h1 v-show="isVisible">Hello, World!</h1>
       </transition>
    </div>
</template>

<script>
export default {
    name: "animate",
    data() {
        return {
            isVisible: true
        }
    }
}
</script>
<style scoped>

@keyframes bounce {
  0%   { transform: scale(0)   }
  50%  { transform: scale(1.5) }
  100% { transform: scale(1)   }
}

.bounce-enter-active {
    animation: bounce .6s linear;
}
.bounce-leave-active {
    animation: bounce .6s linear reverse;
}
</style>
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

效果演示:

【实例 - 04】初始渲染过渡

可以通过 appear 特性设置节点在初始渲染的过渡,所谓初始渲染过渡就是页面在刷新时元素加载的时候出现过渡效果。

<transition appear>
  <!-- ... -->
</transition>
1
2
3

提示:可以在属性中声明 JavaScript 钩子,用于监听过渡的状态

# 4、三方库动画应用

我们可以通过以下特性来自定义过渡类名:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。

> 安装

$ npm install animate.css
1

> 导入

import 'animate.css'
1

> 使用

<template>
  <div class="anima">
    <button type="button" @click="isShow = !isShow">TOGGLE</button>
    <transition 
    enter-active-class="animated rubberBand" 
    leave-active-class="animated rotateOut">
      <h1 v-show="isShow">Hello, world!</h1>
    </transition>
  </div>
</template>

<script>
import 'animate.css'
export default {
  name: "anima",
  data() {
    return {
      isShow: true
    };
  }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 5、多元素过渡

对于原生标签可以使用 v-if/v-else 。最常见的多标签过渡是一个列表和描述这个列表为空消息的元素:

<transition>
  <table v-if="items.length > 0">
    <!-- ... -->
  </table>
  <p v-else>购物车为空</p>
</transition>
1
2
3
4
5
6

可以这样使用,但是有一点需要注意:

提示:

当有相同标签名的元素切换时,需要通过 key 特性设置唯一的值来标记以让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 <transition> 组件中的多个元素设置 key 是一个更好的实践。

示例:

<transition>
  <button v-if="isEditing" key="save">
    Save
  </button>
  <button v-else key="edit">
    Edit
  </button>
</transition>
1
2
3
4
5
6
7
8

在一些场景中,也可以通过给同一个元素的 key 特性设置不同的状态来代替 v-if 和 v-else,上面的例子可以重写为:

<transition>
  <button :key="isEditing">
    {{ isEditing ? 'Save' : 'Edit' }}
  </button>
</transition>
1
2
3
4
5

使用多个 v-if 的多个元素的过渡可以重写为绑定了动态属性的单个元素过渡。例如:

<transition>
  <button v-if="docState === 'saved'"  key="saved">
    Edit
  </button>
  <button v-if="docState === 'edited'" key="edited">
    Save
  </button>
  <button v-if="docState === 'editing'" key="editing">
    Cancel
  </button>
</transition>
1
2
3
4
5
6
7
8
9
10
11

可以重写为:

<transition>
  <button :key="docState">
    {{ buttonMessage }}
  </button>
</transition>
1
2
3
4
5
// ...
computed: {
  buttonMessage: function () {
    switch (this.docState) {
      case 'saved'  : return 'Edit'
      case 'edited' : return 'Save'
      case 'editing': return 'Cancel'
    }
  }
}
1
2
3
4
5
6
7
8
9
10

-> 过渡模式

<transition> 的默认行为 - 进入和离开同时发生。同时生效的进入和离开的过渡不能满足所有要求,所以 Vue 提供了 过渡模式

in-out:新元素先进行过渡,完成之后当前元素过渡离开。

out-in:当前元素先进行过渡,完成之后新元素过渡进入。

# 6、多个组件的过渡

多个组件的过渡简单很多 - 我们不需要使用 key 特性。相反,我们只需要使用动态组件:

<template>
  <div id="app">
    <!-- 导航栏 -->
    <div class="navigation-bar">{{title}}</div>
    <!-- 内容 -->
    <transition name="slide" mode="out-in">
      <keep-alive>
        <component :is="curPage"></component>
      </keep-alive>
    </transition>
    <!-- 标签栏 -->
    <div class="tabBar">
      <section v-for="(route, index) in routes" 
               :key="index" 
               class="tabBarItem"
               @click="handeTabBarItemClick({route, index})">
          {{route.title}}
      </section>
      <section class="cursor" :style="{left}"></section>
    </div>

  </div>
</template>

<script>

export default {
  name: "App",
  data() {
    return {
      routes: [
        {title: "主页", page: "Home"},
        {title: "新闻", page: "News"},
        {title: "关于", page: "About"},
        {title: "我的", page: "Mine"},
      ],
      left: 0,
      title: "主页",
      curPage: "Home"
    };
  },
  components: {
    Home : { template: `<div style="background:pink;" class="page">主页</div>` },
    News : { template: `<div style="background:green;" class="page">新闻</div>` },
    Mine : { template: `<div style="background:orange;" class="page">我的</div>` },
    About: { template: `<div style="background:blue;"  class="page">关于</div>` }
  },
  methods: {
    handeTabBarItemClick({route, index}) {
      this.curPage = route.page;
      this.title = route.title;
      this.left = (25 * index) + "%";
    }
  }
};
</script>

<style>
* {
  margin: 0; padding: 0;
}
html, body, #app {
  height: 100%;
  overflow: hidden;
}
/* 导航栏 */
.navigation-bar {
  height: 64px;
  background: #444;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* 内容 */
.page {
  height: calc(100% - 64px - 49px);
  background: cornflowerblue;
  color: #fff;
  font-size: 22px;
}
/* 标签栏 */
.tabBar {
  position: relative;
  height: 49px;
  background: #444;
  color: #fff;
  display: flex;
  justify-content: space-around;
  align-items: center;
}
.tabBarItem {
  width: 25%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  z-index: 2;
}

.cursor {
  height: 100%;
  width: 25%;
  transition: all .25s linear;
  background: gray;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
}


/* 过渡 */
.slide-enter-active {
  transition: all .3s linear;
}
.slide-enter-to {
    transform: translate(0);
}
.slide-enter {
    transform: translate(100%);
}
</style>
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

# 7、列表过渡

目前为止,关于过渡我们已经讲到:

  • 单个节点
  • 同一时间渲染多个节点中的一个

那么怎么同时渲染整个列表,比如使用 v-for ?在这种场景中,使用 <transition-group> 组件。在我们深入例子之前,先了解关于这个组件的几个特点:

  • 不同于 <transition>,它会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag 特性更换为其他元素。
  • 过渡模式不可用,因为我们不再相互切换特有的元素。
  • 内部元素 总是需要 提供唯一的 key 属性值。

【实例 - 01】基础示例

现在让我们由一个简单的例子深入,进入和离开的过渡使用之前一样的 CSS 类名。

<template>
  <div id="app">
    <button v-on:click="add">Add</button>
    <button v-on:click="remove">Remove</button>
    <transition-group name="list" tag="ul" class="list">
      <li v-for="item in items" v-bind:key="item" class="list-item">{{ item }}</li>
    </transition-group>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: [1, 2, 3, 4, 5, 6],
      nextNum: 7
    };
  },
  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);
    }
  }
};
</script>

<style>
.list {
  list-style: none;
  padding: 0;
}
.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);
}
</style>
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
50
51

这个例子有个问题,当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,而不是平滑的过渡,我们下面会解决这个问题。

【实例 - 02】列表的排序过渡

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name 属性来自定义前缀,也可以通过 move-class 属性手动设置。

v-move 对于设置过渡的切换时机和过渡曲线非常有用,你会看到如下的例子,我们通过 Lodash工具库 打乱集合顺序。

首先安装 loadash:

$ npm i -S lodash
1

然后引入 loadash:

import _ from 'lodash'
1

接下来编码:

<template>
  <div id="app">
    <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>
</template>

<script>
import _ from "lodash";
export default {
  name: "TransAnim",
  data() {
    return {
      items: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    };
  },
  methods: {
    shuffle() {
      this.items = _.shuffle(this.items);
    }
  }
};
</script>

<style>

.flip-list-move {
  transition: transform 1s;
}
</style>
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

这个看起来很神奇,内部的实现,Vue 使用了一个叫 FLIP 简单的动画队列,使用 transforms 将元素从之前的位置平滑过渡新的位置。

我们将之前实现的例子和这个技术结合,使我们列表的一切变动都会有动画过渡。

<template>
  <div id="app">
    <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>
</template>

<script>
import _ from "lodash";
export default {
  name: "App",
  data() {
    return {
      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);
    },
    shuffle: function() {
      this.items = _.shuffle(this.items);
    }
  }
};
</script>

<style>
.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;
}
</style>
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
50
51
52

# 二、状态过渡

Vue 的过渡系统提供了非常多简单的方法设置进入、离开和列表的动效。那么对于数据元素本身的动效呢,比如:

  • 数字和运算
  • 颜色的显示
  • SVG 节点的位置
  • 元素的大小和其他的属性

所有的原始数字都被事先存储起来,可以直接转换到数字。做到这一步,我们就可以结合 Vue 的响应式和组件系统,使用第三方库来实现切换元素的过渡状态。

-> 状态动画

GSAP

通过侦听器我们能监听到任何数值属性的数值更新。可能听起来很抽象,所以让我们先来看看使用 GreenSock 一个例子:

首先,安装 “gsap” js动画库:

$ npm i -S gsap
1

引入 TweenMax :

import {TweenMax} from 'gsap'
1

html:

<div id="app">
    <input v-model.number="number" type="number" step="20">
    <p>{{ animatedNumber }}</p>
</div>
1
2
3
4

js:

new Vue({
  el: '#app',
  data: {
    number: 0,
    tweenedNumber: 0
  },
  watch: {
      number(newVal) {
          TweenLite.to(this.$data, 0.5, { tweenedNumber: newVal });
      }
  },
  computed: {
      animatedNumber() {
          return this.tweenedNumber.toFixed(0);
      }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

效果显示:

当你把数值更新时,就会触发动画。