这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验
- React框架本身并没有提供任何动画相关的API,所以在React中使用过渡动画我们需要使用一个第三方库 react-transition-group
- Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果
过渡动画
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
<template>
<div>
<button @click="isShow = !isShow">show/hidden</button>
<!--
在整个动画的执行阶段,只是定义了开始状态(开始帧)和结束状态(结束帧)
然后设置了开始帧和结束帧之间相互转换时候的过渡动效
所以整个动画可以被认为是一个过渡动画
-->
<transition name="fade">
<h2 v-if="isShow">message</h2>
</transition>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
/*
在实际执行动画的时候,vue会自动为我们添加上对应的动画效果
.[transtiton组件上设置的name值]-[样式后缀]
*/
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/*
因为默认情况下, 这部分的样式就是浏览器的默认设置的样式,
所以这部分的样式可以省略不进行任何的编写
*/
/*
.fade-leave-from,
.fade-enter-to {
opacity: 1;
}
*/
.fade-enter-active,
.fade-leave-active {
transition: opacity 2s ease;
}
</style>
class的name命名规则如下:
- 如果我们使用的是一个没有name的transition,那么所有的class是以 v- 作为默认前缀
- 如果我们添加了一个name属性,比如
<transtion name="foo">,那么所有的class会以foo-开头
过渡动画class
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理
- 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名
- 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
- 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行
| 样式名 | 生效时机 | 失效时机 | 说明 |
|---|---|---|---|
| v-enter-from | 在元素被插入之前生效 | 在元素被插入之后的下一帧移除 | 定义进入过渡的开始状态 |
| v-enter-to | 在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除) | 在过渡/ 动画完成之后移除 | 定义进入过渡的结束状态 |
| v-enter-active | 在整个进入过渡的阶段中应用, 在元素被插入之前生效 | 过渡/动画完成之后移除 | 定义进入过渡生效时的状态 这个类可以被用来定义进入过渡的过程时间,延迟和曲线函 |
| v-leave-from | 在离开过渡被触发时立刻生效 | 下一帧被移除 | 定义离开过渡的开始状 |
| v-leave-to | 在整个离开过渡的阶段中应用, 在离开过渡被触发时立刻生效 | 在过渡/动画完成之后移除 | 离开过渡的结束状态 这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数 |
| v-leave-active | 过渡/动画完成之后移除在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除) | 在过渡/ 动画完成之后移除 | 定义离开过渡生效时的状态 |
animate动画
<template>
<div>
<!-- 这里包裹div是为了和下面的h2换行显示 -->
<div>
<button @click="isShow = !isShow">show/hidden</button>
</div>
<transition name="fade">
<!--
这里的h2必须设置为行内元素或行内块元素
默认是块元素,那么设置的样式作用在的是h2这一整行
而内容是显示不全一整行的,所以动画的效果会有对应的偏差
-->
<h2 v-if="isShow">message</h2>
</transition>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
h2 {
display: inline-block;
}
.fade-enter-active {
animation: bounce 1s ease;
}
.fade-leave-active {
animation: bounce 1s ease reverse;
}
/* 定义帧动画 */
@keyframes bounce {
0% {
transform: scale(0);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
</style>
动画属性
type
Vue为了知道过渡的完成,内部是在监听 transitionend 或 animationend,到底使用哪一个取决于元素应用的CSS规则
如果我们只是使用了其中的一个,那么Vue能自动识别类型并设置监听
但是如果我们同时使用了过渡和动画,在这个情况下可能某一个动画执行结束时,另外一个动画还没有结束
在这种情况下,我们可以设置 type 属性为 animation 或者 transition 来明确的告知Vue监听的类型
推荐: 不推荐将animation和transition动画一起使用
如果必须一起使用,也应该尽可能的将两种动画的执行时间都设置为一样的
<transition name="fade" type="animation">
<h2 v-if="isShow">message</h2>
</transition>
duration
我们也可以显示的来指定过渡的时间,通过 duration 属性。
单位是ms --- 如果设置了duration 那么在css样式中设置的动画执行时间将会被覆盖
duration可以设置两种类型的值
- number类型:同时设置进入和离开的过渡时间
<transition name="fade" :duration="3000">
<h2 v-if="isShow">message</h2>
</transition>
- object类型:分别设置进入和离开的过渡时间
<transition name="fade" :duration="{enter: 1000, leave: 2000}">
<h2 v-if="isShow">message</h2>
</transition>
mode
默认情况下进入和离开动画是同时发生的
此时在两个元素之间切换的时候存在的问题
我们不希望同时执行进入和离开动画,那么我们需要设置transition的过渡模式:
- in-out: 新元素先进行过渡,完成之后当前元素过渡离开
- out-in: 当前元素先进行过渡,完成之后新元素过渡进入
<transition name="fade" mode="out-in">
<h2 v-if="isShow">message</h2>
<h2 v-else>msg</h2>
</transition>
appear
默认情况下,首次渲染的时候是没有动画的
如果我们希望给他添加上去动画,那么就可以增加另外一个属性 appear
<!-- appear的值是一个boolean值,默认是false -->
<!-- 所以 :appear="true" 可以简写为 appear -->
<transition name="fade" mode="out-in" appear>
<Home v-if="isShow" />
<About v-else />
</transition>
animate.css
如果我们手动一个个来编写这些动画,那么效率是比较低的,所以在开发中我们可能会引用一些第三方库的动画库,比如animate.css
Animate.css是一个已经准备好的、跨平台的动画库为我们的web项目,对于强调、主页、滑动、注意力引导 非常有用
如何使用Animate库呢?
- 第一步:需要安装animate.css库
- 第二步:导入animate.css库的样式
- 第三步:使用animation动画或者animate提供的类
main.js
import { createApp } from 'vue'
import App from './App.vue'
// 全局引入动画相关的样式
import 'animate.css'
createApp(App).mount('#app')
App.vue
<template>
<div>
<div>
<button @click="isShow = !isShow">show/hidden</button>
</div>
<transition name="fade" mode="out-in">
<Home v-if="isShow" />
<About v-else />
</transition>
</div>
</template>
<script>
import About from './components/About.vue'
import Home from './components/Home.vue'
export default {
name: 'App',
components: {
Home,
About
},
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
h2 {
display: inline-block;
}
.fade-enter-active {
/* 这里的样式名可以去animate.css的官网去取 */
animation: bounceInDown 1s ease;
}
.fade-leave-active {
animation: zoomOut 1s ease;
}
</style>
我们还可以通过以下 attribute 来自定义过渡类名
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css. 结合使用十 分有用。
<template>
<div>
<div>
<button @click="isShow = !isShow">show/hidden</button>
</div>
<!-- animate__animated是animate.css所设置的公共样式 -->
<transition
mode="out-in"
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
>
<Home v-if="isShow" />
<About v-else />
</transition>
</div>
</template>
<script>
import About from './components/About.vue'
import Home from './components/Home.vue'
export default {
name: 'App',
components: {
Home,
About
},
data() {
return {
isShow: true
}
}
}
</script>
<style scoped>
h2 {
display: inline-block;
}
.fade-enter-active {
animation: bounceInDown 1s ease;
}
.fade-leave-active {
animation: zoomOut 1s ease;
}
</style>
gsap
在使用动画之前,我们先来看一下transition组件给我们提供的JavaScript钩子,这些钩子可以帮助我们监听动画执行到什么阶段了
注意:
-
当我们使用JavaScript来执行过渡动画时,需要进行 done 回调
-
添加 :css="false",也会让 Vue 会跳过 CSS 的检测,
也就是vue不会再进行对于动画的css样式的设置和检测
除了性能略高之外,这可以避免过渡过程中 CSS 规则的影响
-
每一个钩子函数都有一个参数el,表示的是当前执行动画的元素
-
对于
enter和leave构造函数,有两个参数:-
参数1 ---> el即执行动画的元素
-
参数2 ---> done 是一个函数,需要显示调用,以告知vue,对应的动画生命周期函数动效已经执行完毕
-
某些情况下我们希望通过JavaScript来实现一些动画的效果,这个时候我们可以选择使用gsap库来完成。
什么是gsap呢
- GSAP是The GreenSock Animation Platform(GreenSock动画平台)的缩写
- 它可以通过JavaScript为CSS属性、SVG、Canvas等设置动画,并且是浏览器兼容的
这个库应该如何使用呢
- 第一步: 需要安装gsap库
- 第二步: 导入gsap库
- 第三步: 使用对应的api即可
<template>
<div>
<div>
<button @click="isShow = !isShow">show/hidden</button>
</div>
<transition
mode="out-in"
@enter="handleEnter"
@leave="handleLeave"
:css="false"
>
<Home v-if="isShow" />
<About v-else />
</transition>
</div>
</template>
<script>
import About from './components/About.vue'
import Home from './components/Home.vue'
import gsap from 'gsap'
export default {
name: 'App',
components: {
Home,
About
},
data() {
return {
isShow: true
}
},
methods: {
// enter 和 leave回调中,都有两个参数
// 参数1: 执行回调的元素
// 参数2: 手动告诉vue动画执行完毕了,否则对应动画将一直执行下去,会影响后续动画的执行
handleEnter(el, done) {
// from函数表示的是以什么样的状态去还原到初始状态
gsap.from(el, {
scale: 1.2,
opacity: 0,
onComplete: done
})
},
handleLeave(el, done) {
// to表示的是初始状态需要变化成什么样的状态
gsap.to(el, {
scale: 1.2,
opacity: 0,
onComplete: done
})
}
}
}
</script>
<style scoped>
h2 {
display: inline-block;
}
.fade-enter-active {
animation: bounceInDown 1s ease;
}
.fade-leave-active {
animation: zoomOut 1s ease;
}
</style>
gsap实现数字变化
在一些项目中,我们会见到数字快速变化的动画效果,这个动画可以很容易通过gsap来实
<template>
<div style="display: inline-block">
<!--
每次变化的时候,默认存在小数点的变化
所以使用toFixed来去除小数点,进行取整操作
-->
<div>{{ counter.toFixed(0) }}</div>
<button @click="increment">+ 100</button>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
name: 'Home',
data() {
return {
counter: 0
}
},
methods: {
increment() {
// to方法的第一个参数是一个普通对象
// 增长100的值所需要使用的时间是2s
// 变化是couter + 100
// gsap发现变化的是数字的时候,会自动执行数字的递增或递减效果
gsap.to(this, { duration: 2, counter: this.counter + 100 })
}
}
}
</script>
列表操作
之前的所有过渡动画,我们只要是针对单个元素或者组件的
- 要么是单个节点
- 要么是同一时间渲染多个节点中的一个
那么如果希望渲染的是一个列表,并且该列表中添加删除数据也希望有动画执行呢?
这个时候我们要使用 <transition-group> 组件来完成;
使用<transition-group> 有如下的特点:
- 默认情况下,它不会渲染一个元素的包裹器,但是你可以指定一个元素并以 tag attribute 进行渲染
- 过渡模式(in-out 和 out-in)不可用,因为我们不再相互切换特有的元素
- 内部元素总是需要提供唯一的 key attribute值,用以标识动画具体需要添加在哪一个元素上
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身
对于
transaction-group的样式设置方式和对应的动画生命周期函数 和transaction组件是一致的
基本使用
案例:
- 一列数字,可以继续添加或者删除数字
- 在添加和删除数字的过程中,对添加的或者移除的数字添加动画
<template>
<div>
<button @click="add">add</button>
<button @click="remove">remove</button>
<button @click="shuffle">shuffle</button>
<transition-group tag="ul" name="foo">
<li v-for="item in list" :key="item">
{{ item }}
</li>
</transition-group>
</div>
</template>
<script>
import _ from 'lodash'
export default {
name: 'App',
data() {
return {
list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
length: 10
}
},
methods: {
add() {
this.list.splice(this.getRandomIndex(), 0, this.length++)
},
remove() {
this.list.splice(this.getRandomIndex(), 1)
},
shuffle() {
// 洗牌
this.list = _.shuffle(this.list)
},
getRandomIndex() {
return Math.floor(Math.random() * this.list.length) + 1
}
}
}
</script>
<style scoped>
ul,
li {
list-style: none;
margin: 0;
padding: 0;
}
ul {
/*
不应该设置布局为flex布局,因为flex布局会有自己的特殊的渲染方式
所以在进行动画的时候,可能会受到flex布局特殊渲染方式的影响
从而导致动画的执行可能存在问题
*/
/* display: flex; */
margin-top: 10px;
}
li {
margin-right: 10px;
display: inline-block;
}
.foo-enter-from,
.foo-leave-to {
opacity: 0;
transform: translateY(30px);
}
.foo-enter-active,
.foo-leave-active {
transition: all 1s ease;
}
.foo-leave-active {
/*
默认情况下,在移除元素的时候,后边的元素无法进行左移动画
因为会被还没有被及时移除的元素阻挡,需要等待元素移除后在位移
此时动画已经指向时间完毕了,所以会出现闪跳的效果
所以此时可以给离开的元素设置定位,让其脱离标准流,
此时就不会阻挡后续元素的左移操作
从而让位移动画和移除动画同时执行
*/
position: absolute;
}
/*
v-move 是 transition-group特有的样式
默认情况下,新增的或者删除的节点是有动画的,但是对于哪些其他需要移动的节点是没有动画的
所以需要加上这个样式类,用于在插入和移除的时候,添加前后样式的位移动画
这个样式, 它会在元素改变位置的过程中应用
像之前的名字一样,我们可以通过name来自定义前缀
*/
.foo-move {
/* 只要设置过渡属性即可,其余的属性,vue会自动进行相应的计算 */
transition: transform 1s ease;
}
</style>
交错过渡案例
所谓交错过渡动画效果,就是多个元素在执行对应动画的时候,并不是一起执行的,而是一个接着一个进行执行的
<template>
<div>
<input type="text" v-model="search">
<transition-group
tag="ul"
name="list"
@before-enter="beforeEnter"
@enter="handleEnter"
@leave="handleLeave"
:css="false"
>
<!--
因为enter和leave方法是绑定在li
设置自定义属性,以便于在js代码中可以获取到li的索引值
-->
<li v-for="(user, index) in showUsers" :key="user" :data-index="index">{{ user }}</li>
</transition-group>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
name: 'App',
data() {
return {
search: '',
users: ['Bruce Lee','Jackie Chan','Chuck Norris','Jet Li','Kung Fury']
}
},
computed: {
showUsers() {
return this.users.filter(user => user.includes(this.search))
}
},
methods: {
// 样式初始化
beforeEnter(el) {
el.style.height = '0px'
el.style.opacity = '0'
},
handleEnter(el, done) {
// 我们来通过gsap的延迟delay属性,实现一个交替消失的动画
gsap.to(el, {
height: '1.5em',
opacity: 1,
delay: el.dataset.index * 0.1, // 获取索引,从而实现delay值依次递增的效果
onComplete: done
})
},
handleLeave(el, done) {
gsap.to(el, {
height: 0,
opacity: 0,
delay: el.dataset.index * 0.1,
onComplete: done
})
}
}
}
</script>