前言
本来对过渡和动画这块没怎么重视,直到我发现了这个:
我知道我应该好好学习过渡和动画了。这个功能来自于Element的Tabs标签页组件,感兴趣的可以自己去试试。
过渡和动画概述
基于class和style的过渡
略
Transform 和 Opacity
官方文档说Transform 和 Opacity不会触发重绘,因此可以用来提升性能。比如可以使用transform
来代替top
、left
的移动。
硬件加速
诸如 perspective、backface-visibility 和 transform:translateZ(x) 等 property 将让浏览器知道你需要硬件加速。
如果要对一个元素进行硬件加速,可以应用以下任何一个 property (并不是需要全部,任意一个就可以):
perspective: 1000px; //这个属性允许你改变3D元素是怎样查看透视图。
backface-visibility: hidden; //定义当元素背面向屏幕时是否可见。
transform: translateZ(0); //平移
缓动效果
缓动效果是在动画中表达深度的一个重要方式。动画新手最常犯的一个错误是在起始动画节点使用 ease-in,在结束动画节点使用 ease-out。实际上你需要的是反过来的。
进入过渡和离开过渡
单元素过渡
Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用 v-if)
- 条件展示 (使用 v-show)
- 动态组件
- 组件根节点
当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:
1、自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
2、如果过渡组件提供了 JavaScript 钩子函数 ,这些钩子函数将在恰当的时机被调用。
3、如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此处指浏览器逐帧动画机制,和 Vue 的 nextTick 概念不同)
这里只介绍class方式的过渡,JavaScript钩子函数的方式几乎不适用,可以自己看官方文档。
在进入/离开的过渡中,会有 6 个 class 切换。
1、v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
2、v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
3、v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
4、v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
5、v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
6、v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。
关于class类的命名略
demo:Vue 路由切换动画
一般情况下过渡类只会用到下面的4个类,并且基本是成对出现:
enter-active
和 leave-active
表示过渡生效时的状态;enter-from
和leave-to
表示过渡的开始状态
多个组件之间的过渡
组件之间的过渡更简单——我们甚至不需要 key 属性。取而代之的是,我们包裹了一个动态组件 :
官方demo:
<div id="demo">
<input v-model="view" type="radio" value="v-a" id="a"><label for="a">A</label>
<input v-model="view" type="radio" value="v-b" id="b"><label for="b">B</label>
<transition name="component-fade" mode="out-in">
<component :is="view"></component>
</transition>
</div>
.component-fade-enter-active,
.component-fade-leave-active {
transition: opacity 0.3s ease;
}
.component-fade-enter-from,
.component-fade-leave-to {
opacity: 0;
}
列表过渡
我们回到文章开头的那个过渡效果,怎么同时渲染整个列表,比如使用 v-for?在这种场景下,我们会使用 <transition-group>
组件。
特点:
- 默认情况下,它不会渲染一个包裹元素,但是你可以通过 tag attribute 指定渲染一个元素。
- 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的 key attribute 值。
- CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
列表的进入/离开过渡
应用:增删数组中的元素
<template>
<div>
<div>
<el-button type="primary" size="small" @click="addNumber">添加</el-button>
<el-button type="primary" size="small" @click="delNumber">移出</el-button>
</div>
<transition-group name="list-complete" class="list" tag="div">
<div class="list-item" v-for="item in list" :key="item">
<span>{
{
item }}</span>
</div>
</transition-group>
</div>
</template>
<script setup lang="ts">
import {
ref } from 'vue'
let list = ref([1, 2, 3, 4])
let addNumber = () => {
list.value.push(list.value.length + 1)
}
let delNumber = () => {
list.value.splice(list.value.length - 1, 1)
}
</script>
<style scoped lang="scss">
.list {
display: flex;
width: 1000px;
height: 22px;
margin-top: 30px;
.list-item {
width: 80px;
height: 20px;
line-height: 20px;
text-align: center;
border: 1px solid #ddd;
transition: all 0.8s ease;
}
}
.list-complete-enter-from {
transform: translateX(-10px);
opacity: 0;
}
.list-complete-leave-to {
transform: translateX(-5px);
opacity: 0;
}
.list-complete-enter-active,
.list-complete-leave-active {
transition: opacity 0.3s ease;
}
</style>
tag="div"
表示transition-group
最后被渲染成div
标签
效果
列表的交错过渡
应用:搜索过滤
通过 data attribute 与 JavaScript 通信,就可以实现列表的交错过渡,我们可以基于官方demo进行简单修改
gsap
是一个高效的js
动画库,既然vuedemo里都用到了这个库,那么这是非常值得学习的
<template>
<div>
<el-row :gutter="20">
<el-col :span="4">
<el-input v-model="filterText" placeholder="请输入关键字"></el-input>
</el-col>
<el-col :span="2">
<el-button type="primary" @click="search">搜索</el-button>
</el-col>
</el-row>
<transition-group name="staggered-fade" tag="ul" :css="false" @before-enter="beforeEnter" @enter="enter"
@leave="leave">
<li v-for="(item, index) in result" :key="item" :data-index="index">
{
{
item }}
</li>
</transition-group>
</div>
</template>
<script setup lang="ts">
import gsap from 'gsap'
import {
ref } from 'vue'
let list = ref(['东方耀', '孙悟空', '猪八戒', '李白', '东方镜', '李信'])
let result = ref(['东方耀', '孙悟空', '猪八戒', '李白', '东方镜', '李信'])
let filterText = ref('')
let search = () => {
result.value = list.value.filter(e => e.includes(filterText.value))
}
let beforeEnter = (el) => {
el.style.opacity = 0
el.style.height = 0
}
let enter = (el, done) => {
gsap.to(el, {
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
})
}
let leave = (el, done) => {
gsap.to(el, {
opacity: 0,
height: 0,
delay: el.dataset.index * 0.15,
onComplete: done
})
}
</script>
<style scoped lang="scss">
.list {
margin-top: 30px;
.list-item {
height: 40px;
line-height: 40px;
}
}
</style>
效果