文章目录
- Transition&TransitionGroup动画组件
- 1. Transition
- 1.1 Transition API
- 1.2 基于CSS的过渡效果
- 自定义过渡class + Animate动画库
- 同时使用 transition 和 animation
- 深层级过渡与显式过渡时长
- duration(总持续时间)
- 1.3 JavaScript 钩子 + GreenSock 库
- 案例
- 1.4 可复用过渡效果
- 1.5 出现时过渡(appear)
- 1.6 元素间过渡与组件间过渡
- 1.7 过渡模式
- 2. TransitionGroup
- 2.1 与Transition的区别
- 2.2 基本使用
- 2.3 案例
Transition&TransitionGroup动画组件
官方文档:Transition
Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画:
<Transition>
会在一个元素或组件进入和离开 DOM 时应用动画。<TransitionGroup>
会在一个 v-for 列表中的元素或组件被插入,移动,或移除时应用动画。
1. Transition
它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:
- 由
v-if
所触发的切换 - 由
v-show
所触发的切换 - 由特殊元素
<component>
切换的动态组件
注意: <Transition>
仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。
1.1 Transition API
props
interface TransitionProps {
/**
* 用于自动生成过渡 CSS class 名。
* 例如 `name: 'fade'` 将自动扩展为 `.fade-enter`、
* `.fade-enter-active` 等。
*/
name?: string
/**
* 是否应用 CSS 过渡 class。
* 默认:true
*/
css?: boolean
/**
* 指定要等待的过渡事件类型
* 来确定过渡结束的时间。
* 默认情况下会自动检测
* 持续时间较长的类型。
*/
type?: 'transition' | 'animation'
/**
* 显式指定过渡的持续时间。
* 默认情况下是等待过渡效果的根元素的第一个 `transitionend`
* 或`animationend`事件。
*/
duration?: number | { enter: number; leave: number }
/**
* 控制离开/进入过渡的时序。
* 默认情况下是同时的。
*/
mode?: 'in-out' | 'out-in' | 'default'
/**
* 是否对初始渲染使用过渡。
* 默认:false
*/
appear?: boolean
/**
* 用于自定义过渡 class 的 prop。
* 在模板中使用短横线命名,例如:enter-from-class="xxx"
*/
enterFromClass?: string
enterActiveClass?: string
enterToClass?: string
appearFromClass?: string
appearActiveClass?: string
appearToClass?: string
leaveFromClass?: string
leaveActiveClass?: string
leaveToClass?: string
}
事件
@before-enter
@before-leave
@enter
@leave
@appear
@after-enter
@after-leave
@after-appear
@enter-cancelled
@leave-cancelled (v-show only)
@appear-cancelled
1.2 基于CSS的过渡效果
- 我们可以给
<Transition>
组件传一个name
prop 来声明一个过渡效果名。<Transition>
如果没有命名,则类名默认以v
作前缀。 - 一般情况下(显示/消失动画同类型),
v-enter-from
和v-leave-to
、v-enter-active
和v-leave-active
、v-enter-to
和v-leave-from
样式一致
v-enter-from
:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。v-enter-active
:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。(即:animation
或transition
)v-enter-to
:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。v-leave-from
:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。v-leave-active
:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。(即:animation
或transition
)v-leave-to
:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。
<template>
<el-button @click="isShow = !isShow">切 换</el-button>
<hr class="mtb20">
<transition>
<div v-if="isShow" class="box"></div>
</transition>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isShow = ref<boolean>(true)
</script>
使用transition
.box {
width: 200px;
height: 200px;
background-color: aqua;
}
// .fade-enter-from:显示——初始状态
// .fade-leave-to:离开——结束状态
.fade-enter-from,
.fade-leave-to {
width: 0;
height: 0;
}
// .fade-enter-active:显示——过渡效果
// .fade-leave-active:离开——过渡效果
.fade-enter-active,
.fade-leave-active {
transition: all 1s ease;
}
// .fade-enter-to:显示——结束状态
// .fade-leave-from:离开——初始状态
.fade-enter-to {
width: 200px;
height: 200px;
}
使用animation
.box {
width: 200px;
height: 200px;
background-color: aqua;
}
// .fade-enter-active:显示——过渡效果
.fade-enter-active {
animation: bounce-in 1s;
}
// .fade-leave-active:离开——过渡效果
.fade-leave-active {
animation: bounce-in 1s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
自定义过渡class + Animate动画库
<transition
name="fade"
enterFromClass="from"
enterActiveClass="active"
enterToClass="to"
leaveFromClass="to"
leaveActiveClass="active"
leaveToClass="from"
>
<div v-if="isShow" class="box"></div>
</transition>
.from {
width: 0;
height: 0;
}
.active {
transition: all 1s ease;
}
.to {
width: 200px;
height: 200px;
}
传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用,比如 Animate.css:
<transition
name="fade"
enterActiveClass="animate__animated animate__bounceIn"
leaveActiveClass="animate__animated animate__bounceOut"
>
<div v-if="isShow" class="box"></div>
</transition>
同时使用 transition 和 animation
Vue 需要附加事件监听器,以便知道过渡何时结束。可以是 transitionend
或 animationend
,这取决于你所应用的 CSS 规则。如果你仅仅使用二者的其中之一,Vue 可以自动探测到正确的类型。
然而在某些场景中,你或许想要在同一个元素上同时使用它们两个。举例来说,Vue 触发了一个 CSS 动画,同时鼠标悬停触发另一个 CSS 过渡。此时你需要显式地传入 type
prop 来声明,告诉 Vue 需要关心哪种类型,传入的值是 animation
或 transition
:
<Transition type="animation">...</Transition>
深层级过渡与显式过渡时长
尽管过渡 class
仅能应用在 <Transition>
的直接子元素上,我们还是可以使用深层级的 CSS 选择器,在深层级的元素上触发过渡效果。
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
/* 应用于嵌套元素的规则 */
.nested-enter-active .inner,
.nested-leave-active .inner {
transition: all 0.3s ease-in-out;
}
.nested-enter-from .inner,
.nested-leave-to .inner {
transform: translateX(30px);
opacity: 0;
}
duration(总持续时间)
可以通过向 <Transition>
组件传入 duration
prop 来显式指定过渡的持续时间 (以毫秒为单位)。总持续时间应该匹配延迟加上内部元素的过渡持续时间:
<Transition :duration="550">...</Transition>
<style>
.v-enter-active,
.v-leave-active {
transition: all 0.3s ease-in-out;
transition-delay: 0.25s;
}
</style>
可以分开指定进入离开时间
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
1.3 JavaScript 钩子 + GreenSock 库
@before-enter
:对应 enter-from
状态
@enter
:对应 enter-active
状态
@after-enter
:对应 enter-to
状态
@enter-cancelled
:显示过渡打断
@before-leave
:对应 leave-from
状态
@leave
:对应 leave-active
状态
@after-leave
:对应 leave-to
状态
@leave-cancelled
:离开过渡打断
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el: Element) {
console.log('进入之前');
}
// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el: Element, done: Function) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
console.log('进入过渡曲线');
done() // 默认同步
}
// 当进入过渡完成时调用。
function onAfterEnter(el: Element) {
console.log('进入过渡完成');
}
function onEnterCancelled(el: Element) {
console.log('进入过渡效果被打断');
}
// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el: Element) {
console.log('离开之前');
}
// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el: Element, done: Function) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
console.log('离开过渡曲线');
setTimeout(() => {
done() // 两秒后显示 离开完成,若在时间内切换则显示 过渡打断
}, 2000)
}
// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el: Element) {
console.log('离开过渡完成');
}
// 仅在 v-show 过渡中可用
function onLeaveCancelled(el: Element) {
console.log('离开过渡效果被打断');
}
在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css="false"
prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。
<Transition
...
:css="false"
>
...
</Transition>
配合动画库 GreenSock库
npm install gsap
案例
<template>
<el-button @click="isShow = !isShow">切 换</el-button>
<hr class="mtb20">
<transition :css="false" @beforeEnter="onBeforeEnter" @enter="onEnter" @leave="onLeave">
<div v-if="isShow" class="box"></div>
</transition>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import gsap from 'gsap'
const isShow = ref<boolean>(false)
// gsap: 第一个参数可以接收一个el,也可以接收一个对象
const onBeforeEnter = (el: Element) => {
gsap.set(el, {
scaleX: 0.25,
scaleY: 0.25,
opacity: 1
})
}
const onEnter = (el: Element, done: gsap.Callback) => {
gsap.to(el, {
duration: 1,
scaleX: 1,
scaleY: 1,
opacity: 1,
ease: 'elastic.inOut(2.5, 1)',
onComplete: done
})
}
const onLeave = (el: Element, done: gsap.Callback) => {
gsap.to(el, {
duration: 0.7,
scaleX: 1,
scaleY: 1,
x: 300,
ease: 'elastic.inOut(2.5, 1)'
})
gsap.to(el, {
duration: 0.2,
delay: 0.5,
opacity: 0,
onComplete: done
})
}
</script>
<style lang="less" scoped>
.box {
width: 200px;
height: 200px;
background-color: aqua;
}
</style>
1.4 可复用过渡效果
<!-- MyTransition.vue -->
<script>
// JavaScript 钩子逻辑...
</script>
<template>
<!-- 包装内置的 Transition 组件 -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- 向内传递插槽内容 -->
</Transition>
</template>
<style>
/*
必要的 CSS...
注意:避免在这里使用 <style scoped>
因为那不会应用到插槽内容上
*/
</style>
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>
1.5 出现时过渡(appear)
如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 appear
prop:
<!--
appearFromClass: 类似enterFromClass
appearActiveClass: 类似enterActiveClass
appearToClass: 类似enterToClass
-->
<transition
appear
appearFromClass="from"
appearActiveClass="active"
appearToClass="to"
>
<div v-if="isShow" class="box"></div>
</transition>
1.6 元素间过渡与组件间过渡
<!-- 元素间过渡 -->
<Transition name="slide-up">
<button v-if="docState === 'saved'"
@click="docState = 'edited'">Edit</button>
<button v-else-if="docState === 'edited'"
@click="docState = 'editing'">Save</button>
<button v-else-if="docState === 'editing'"
@click="docState = 'saved'">Cancel</button>
</Transition>
<!-- 组件间过渡 -->
<!-- 一般添加过渡模式:out-in -->
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>
1.7 过渡模式
<!--
default:出现消失动画同时进行
out-in:先进行离开动画,后进行出现动画
in-out: 先进行出现动画,后进行离开动画(不常用)
-->
<Transition mode="out-in">
...
</Transition>
2. TransitionGroup
2.1 与Transition的区别
- 默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
- 过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
- 列表中的每个元素都必须有一个独一无二的
key
attribute。 - CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
2.2 基本使用
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
<style>
/* .list-move:对移动中的元素应用的过渡 */
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.list-leave-active {
position: absolute;
}
</style>
2.3 案例
<template>
<input v-model="name" />
<hr class="mtb20">
<transition-group tag="ul" :css="false" @beforeEnter="onBeforeEnter" @enter="onEnter" @leave="onLeave">
<li class="mtb20" :key="item.name" v-for="(item, index) in currentList" :data-index="index">{{ item.name }}</li>
</transition-group>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';
import gsap from 'gsap'
type List = {
name: string
}
const name = ref<string>('')
const list: List[] = reactive([{
name: '多多'
}, {
name: '小多'
}, {
name: '小凡'
}, {
name: '图图'
}])
const currentList = computed<List[]>(() => {
return list.filter((item) => item.name.toLowerCase().includes(name.value))
})
const onBeforeEnter = (el: Element) => {
gsap.set(el, {
opacity: 0,
height: 0
})
}
const onEnter = (el: Element, done: gsap.Callback) => {
const index = Number((el as HTMLLIElement).dataset.index)
gsap.to(el, {
opacity: 1,
height: 'auto',
delay: index * 0.15,
onComplete: done
})
}
const onLeave = (el: Element, done: gsap.Callback) => {
const index = Number((el as HTMLLIElement).dataset.index)
gsap.to(el, {
opacity: 0,
height: 0,
delay: index * 0.15,
onComplete: done
})
}
</script>