过渡与动画基础
transform 形变、transition 过渡动画、animation 帧动画
Vue 的 transition 过渡
在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验:
React框架本身并没有提供任何动画相关的API,所以在React中使用过渡动画我们需要使用一个第三方库
- react-transition-group;
Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果;
- vue 中给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画。
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:
- 条件渲染 (使用 v-if)条件展示 (使用 v-show)
- 动态组件
组件根节点 ```html
hello VUE
如果要添加展示到隐藏的过渡效果:**整个过程就像往返跑**。
```css
.dd-enter-from,
.dd-leave-to { // 一般用并集选择器写成一对,因为通常离开后要到达的目标状态和最开始的状态是一样的
opacity: 0;
}
.dd-enter-to,
.dd-leave-from {
opacity: 1;
}
.dd-enter-active,
.dd-leave-active {
transition: opacity 1s ease;
}
仅支持单个元素或组件作为其插槽内容 。如果内容是一个组件,这个组件必须仅有一个根元素。
Transition 组件的原理
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名;
- 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用;
- 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行;
一句话:原理就是自动嗅探 style 标签,自动添加类名到标签上。
添加的 css 类名
v-enter-from
:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。v-enter-active
:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。v-enter-to
:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。v-leave-from
:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。v-leave-active
:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。v-leave-to
:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。过渡效果名
我们可以给组件传一个 name prop 来声明一个过渡效果名:对于一个有名字的过渡效果,对它起作用的过渡 class 会以其名字而不是 v 作为前缀。比如,上方例子中被应用的 class 将会是 fade-enter-active 而不是 v-enter-active。 CSS 的 transition
前后两个状态也是可以有持续时间,而不是一个时刻的快照,所以前后状态可以添加 css transition 过渡效果,实现更细腻的过渡。 ```css / 进入和离开动画可以使用不同 持续时间和速度曲线。 / .slide-fade-enter-active { transition: all 0.3s ease-out; }
.slide-fade-leave-active { transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1); }
.slide-fade-enter-from, .slide-fade-leave-to { transform: translateX(20px); opacity: 0; }
<a name="iSD5E"></a>
## animation 帧动画
前面我们是通过transition来实现的动画效果,另外我们也可以通过animation来实现。
```css
.dd-enter-active {
animation: bounce 1s ease;
}
.dd-leave-active {
animation: bounce 1s ease reverse; // 离开的动画可以直接取反
}
@keyframes bounce {
0% {
transform: scale(0)
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
Transition 组件的属性
type 动画类型
Vue为了知道过渡的完成,内部是在监听 transitionend 或 animationend,到底使用哪一个取决于元素应用的
CSS规则:如果我们只是使用了其中的一个,那么Vue能自动识别类型并设置监听;
如果同时设置过渡和动画,而且动画时间不一致,那是以谁的动画为主呢?
在这种情况下,我们可以设置 type 属性为 animation 或者 transition 来明确的告知Vue监听的类型,也就是以谁的动画为主。
<transition name="dd" type="animation"> ... </transition>
duration 持续时间
对于同时设置了两种动画类型的问题,我们也可以显示的来指定过渡的时间,通过 duration 属性。
duration可以设置两种类型的值:
- number类型:同时设置进入和离开的过渡时间;
- object类型:分别设置进入和离开的过渡时间;
设置 duration 后过渡和动画定义的时间就无效了。
<transition name="dd" type="animation" :duration="1000"> ... </transition>
<transition
name="dd"
type="animation"
:duration="{ enter: 800, leave: 1000 }">
...
</transition>
mode 过渡的模式
两个元素都有动画,那两个元素切换时动画就会有一个时间上的重合问题。
比如点击按钮元素 A 消失,元素 B 出现。那么会出现 A 在执行消失动画的时候,B 的进入动画同时也在执行的情况。离开动画和进入动画是同时的。
- 动画在两个元素之间切换的时候存在的问题:
hello world 的消失动画没执行完,把后面进入的元素位置挤走了。
所以在多个元素切换的时候,我们需要给动画设置过渡模式,也就是执行动画的先后。
mode 属性:
- out-in:离开动画执行完,再执行新元素的进入动画
- in-out:先执行新元素的进入动画,再执行旧元素的离开动画,也就是说页面会同时出现新旧两个元素。
这种切换问题不止出现在两个元素的切换中,还发生在组件切换中,比如路由切换,动态组件切换。
appear 初次渲染
默认情况下,首次渲染的时候是没有动画的,如果我们希望给他添加上去动画,那么就可以增加另外一个属性 appear
- 默认是 false,可以改成 true。v-bind:appear=”true”,简写直接写个 appear ,和 checked 一样。
Transition 组件的应用场景
一般动态组件或者路由的插槽,缓存组件,过渡动画组件一起连用。<router-view v-slot="hhh"> <!-- <router-view v-slot ='{ Component }'> --> <transition name="ddd"> <keep-alive> <component :is="hhh.Component"></component> </keep-alive> </transition> </router-view>
animate.css 动画库
animate.css 有两种使用方式:
- 帧动画使用
- 类名使用
帧动画使用
官网中的动画名就是 @keyframes 的变量名。
```css .dd-enter-active { animation: fadeInLeft 1s ease; }
.dd-leave-active { animation: fadeInLeft 1s ease reverse; }
<a name="dWoUW"></a>
## 类名使用
除了使用帧动画,也可以使用类名。右边复制按钮就能复制类名。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1661002957149-f47b51d0-c70f-40b7-9e54-d4adb06904f2.png#clientId=ud13c7fbf-9594-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=85&id=u50ca549d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=133&originWidth=283&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10404&status=error&style=none&taskId=ue51649dc-edd9-4c99-aa6a-66bb9cbc3eb&title=&width=181.12)<br />注意:使用所有效果的类名之前都要加上基础动画的类
```css
<h1 class="animate__animated animate__fadeInLeft">An animated element</h1>
但是像上面一样使用这些类名,transition 组件并不会识别。过渡效果的类,它默认只识别 v-enter-from 这些。
那怎么让其他的动画效果 class 生效呢?
transition 自定义过渡类
transition 组件提供以下 attribute 来让我们自定义过渡类名:这些属性也对应了动画的时间点。
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
他们的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css. 结合使用十
分有用。
<transition name="dd" enter-active-class="animate__animated animate__fadeInLeft" appear>
<transition
name="dd"
enter-active-class="animate__animated animate__fadeInLeft"
leave-active-class="animate__animated animate__fadeInLeft"
// leave-active-class="animate__animated animate__fadeInRight"
appear
mode="out-in"
>
<h1 v-if="isShow">hello VUE</h1>
</transition>
<style scoped lang="less">
// 可以给 animate.css 的类进行自定义,比如上面离开也是从左边淡入,显然不可能,所以可以将从左淡入添加反转
// 当然也可以直接给离开动画属性添加向右淡出的类名
.animate__fadeInLeft {
animation-direction: reverse;
}
</style>
gsap 动画库
JavaScript 钩子
在使用动画之前,我们先来看一下transition组件给我们提供的JavaScript钩子,这些钩子可以帮助我们监听动画执行到什么阶段了。
当我们使用JavaScript来执行过渡动画时,需要进行 done 回调,否则它们将会被同步调用,过渡会立即完成。
添加:css="false"
,也会让 Vue 会跳过 CSS 的检测,除了性能略高之外,这可以避免过渡过程中 CSS 规则的影响。
gsap 库的使用
gsap 实现数字变化
在一些项目中,我们会见到数字快速变化的动画效果,这个动画可以很容易通过gsap来实现:
transition-group 列表过渡
transition 组件是给单个元素,或者组件添加动画效果。这个元素可能会包裹很多子元素,但是本质还是动画只给最外层的元素添加了 class 类。
transition-group 是给所有包裹的元素添加动画效果,给所有元素添加了动画效果的 class 类。这样就能做到一种全体动画的效果。
比如一个数组中,每个元素的删除,增加,排序都会有动画效果。
使用
- 默认情况下,它不会渲染一个元素的包裹器,但是你可以指定一个元素并以 tag attribute 进行渲染;
- 比如 tag=”div” 让一个 div 做外层包裹
- 过渡模式 mode 不可用,因为我们不再相互切换特有的元素;
- 内部元素总是需要提供唯一的 key attribute 值;
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身;
注意一点: 动画效果的元素使用行内元素可能会没有效果,比如 span 位移 transform 就没有效果,所以一般要转成行内非替换元素。
列表过渡的基本使用
我们来做一个案例:
- 案例是一列数字,可以继续添加或者删除数字;
- 在添加和删除数字的过程中,对添加的或者移除的数字添加动画;
一个注意点:元素执行消失动画的时候,依然占据 dom 位置,这时后面的元素想要执行往上挤的动画就没效果,卡住了。前面的元素一完全消失,后面的元素就会突然的弹上去,本来的动画效果被卡没了。
所以可以让元素执行消失动画时脱标:display: absolute;
<template>
<div>
<button @click="addNum">添加数字</button>
<button @click="removeNum">删除数字</button>
<button @click="shuffleNum">数字洗牌</button>
<transition-group tag="p" name="why">
<span v-for="item in numbers" :key="item" class="item">
{{item}}
</span>
</transition-group>
</div>
</template>
<script>
import _ from 'lodash';
export default {
data() {
return {
numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
numCounter: 10
}
},
methods: {
addNum() {
// this.numbers.push(this.numCounter++)
this.numbers.splice(this.randomIndex(), 0, this.numCounter++)
},
removeNum() {
this.numbers.splice(this.randomIndex(), 1)
},
shuffleNum() {
this.numbers = _.shuffle(this.numbers); // lodash 将数字打乱洗牌
},
randomIndex() {
return Math.floor(Math.random() * this.numbers.length) // 随机数
}
},
}
</script>
<style scoped>
.item {
margin-right: 10px;
display: inline-block; // 克服 span 无位移效果
}
.why-enter-from,
.why-leave-to {
opacity: 0;
transform: translateY(30px);
}
.why-enter-active,
.why-leave-active {
transition: all 1s ease;
}
.why-leave-active {
position: absolute; // 在执行离开动画时让元素脱标
}
.why-move {
transition: transform 1s ease;
}
</style>
列表的交错过渡案例
我们可以通过gsap的延迟delay属性,让元素一个接一个消失,做一个交替消失的动画:
<template>
<div>
<input v-model="keyword">
<transition-group tag="ul" name="why" :css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave">
<li v-for="(item, index) in showNames" :key="item" :data-index="index">
{{item}}
</li>
</transition-group>
</div>
</template>
<script>
import gsap from 'gsap';
export default {
data() {
return {
names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"],
keyword: ""
}
},
computed: {
showNames() {
return this.names.filter(item => item.indexOf(this.keyword) !== -1)
}
},
methods: {
beforeEnter(el) {
el.style.opacity = 0;
el.style.height = 0;
},
enter(el, done) {
gsap.to(el, {
opacity: 1,
height: "1.5em",
delay: el.dataset.index * 0.5,
onComplete: done
})
},
leave(el, done) {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.5,
onComplete: done
})
}
}
}
</script>
<style scoped>
/* .why-enter-from,
.why-leave-to {
opacity: 0;
}
.why-enter-active,
.why-leave-active {
transition: opacity 1s ease;
} */
</style>