前言
Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画:
- <Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用它。
- <TransitionGroup> 会在一个 v-for 列表中的元素或组件被插入,移动,或移除时应用动画。
我们可以利用这两个组件,再结合css和js快速实现简单实用的过渡和动画效果,下面依次介绍它们。
Transition 组件
介绍
<Transition> 是一个内置组件,这意味着它在任意别的组件中都可以被使用,无需注册。**它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。**进入或离开可以由以下的条件之一触发:
- 由 v-if 所触发的切换
- 由 v-show 所触发的切换
- 由特殊元素 <component> 切换的动态组件
注意:<Transition> 仅支持单个元素或组件作为其插槽内容。如果内容是一个组件,这个组件必须仅有一个根元素。
作用流程
当一个<Transition> 组件中的元素被插入或移除时,会发生下面这些事情:
- Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡 class 会在适当的时机被添加和移除。
- 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
- 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。
和css结合使用
vue中定义了6个应用于元素进入与离开过渡效果的class。
-
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 被移除的同时),在过渡或动画完成之后移除。
<Transition> 组件传一个 name prop 来声明一个过渡效果名,例如:<Transition name=“fade”> ;那么上面的class名中的v需要替换成 name prop,例如: fade-leave-to
自定义class名
你也可以向 传递以下的 props 来指定自定义的过渡 class:
- enter-from-class
- enter-active-class
- enter-to-class
- leave-from-class
- leave-active-class
- leave-to-class
你传入的这些 class 会覆盖相应阶段的默认 class 名。这个功能在你想要在 Vue 的动画机制下集成其他的第三方 CSS 动画库时非常有用。
<!-- 假设你已经在页面中引入了 Animate.css -->
<Transition
name="custom-classes"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</Transition>
同时使用css的tansition和animation和js钩子函数
同时使用css的tansition和animation和js钩子函数时,需要显式指定js钩子函数监听的类型。传入的值是 animation 或 transition:
<Transition type="animation">...</Transition>
示例
<template>
<div class="container">
<h1>过渡效果- 内置组件Transition</h1>
<div class="example-one">
<button @click="show = !show">Toggle</button>
<Transition>
<p v-show="show">hello</p>
</Transition>
</div>
<div class="example-two">
<button @click="show2 = !show2">Toggle2</button>
<Transition name="bounce">
<p v-if="show2" style="text-align: center">
Hello here is some bouncy text!
</p>
</Transition>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onBeforeMount, onMounted, ref } from "vue";
const show = ref(true);
const show2 = ref(true);
show2.value = false;
</script>
<style lang="scss" scoped>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
// 示例2
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
</style>
和js结合使用
通过监听 <Transition> 组件事件的方式在过渡过程中挂上钩子函数:
- before-enter : 在元素被插入到 DOM 之前被调用。用这个来设置元素的 “enter-from” 状态
- enter : 在元素被插入到 DOM 之后的下一帧被调用。用这个来开始进入动画。
- after-enter 和enter-cancelled : 当进入过渡完成时调用。
- before-leave : 在 leave 钩子之前调用。大多数时候,你应该只会用到 leave 钩子。
- leave : 在离开过渡开始时调用。 用这个来开始离开动画。
- after-leave : 在离开过渡完成、且元素已从 DOM 中移除时调用。
- leave-cancelled : 仅在 v-show 过渡中可用。
这些钩子可以与 CSS 过渡或动画结合使用,也可以单独使用。在使用仅由 JavaScript 执行的动画时,最好是添加一个 :css=“false” prop。这显式地向 Vue 表明可以跳过对 CSS 过渡的自动探测。除了性能稍好一些之外,还可以防止 CSS 规则意外地干扰过渡效果。
<template>
<div class="container">
<h1>过渡效果- 内置组件Transition</h1>
<div class="example-one">
<button @click="show = !show">Toggle</button>
<Transition>
<p v-show="show">hello</p>
</Transition>
</div>
<div class="example-two">
<button @click="show2 = !show2">Toggle2</button>
<Transition name="bounce" appear>
<p v-if="show2" style="text-align: center">
Hello here is some bouncy text!
</p>
</Transition>
</div>
<h2>js</h2>
<div>
<Transition
appear
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<div v-show="show2">这是一个box</div>
</Transition>
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs, onBeforeMount, onMounted, ref } from "vue";
const show = ref(true);
const show2 = ref(true);
// 在元素被插入到 DOM 之前被调用
// 用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el: Element) {
console.log("在元素被插入到 DOM 之前被调用", el);
}
// 在元素被插入到 DOM 之后的下一帧被调用
// 用这个来开始进入动画
function onEnter(el: Element, done: any) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
console.log("在元素被插入到 DOM 之后的下一帧被调用", el);
done();
}
// 当进入过渡完成时调用。
function onAfterEnter(el: Element) {
console.log("进入过渡完成 --- onAfterEnter");
}
function onEnterCancelled(el: Element) {
console.log("进入过渡完成 --- onEnterCancelled");
}
// 在 leave 钩子之前调用
// 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el: Element) {
console.log("在 leave 钩子之前调用");
}
// 在离开过渡开始时调用
// 用这个来开始离开动画
function onLeave(el: Element, done: any) {
// 调用回调函数 done 表示过渡结束
// 如果与 CSS 结合使用,则这个回调是可选参数
console.log("在离开过渡开始时调用", el);
done();
}
// 在离开过渡完成、
// 且元素已从 DOM 中移除时调用
function onAfterLeave(el: Element) {
console.log("在离开过渡完成、且元素已从 DOM 中移除时调用 --- onAfterLeave");
}
// 仅在 v-show 过渡中可用
function onLeaveCancelled(el: Element) {
console.log(
"在离开过渡完成、且元素已从 DOM 中移除时调用 仅在 v-show 过渡中可用 --- onLeaveCancelled"
);
}
</script>
<style lang="scss" scoped>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
// 示例2
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
</style>
和vue-router结合使用
在使用vue-router时可以利用transition实现过渡效果。不过,需要注意的是,实现过渡效果的组件,只能有一个根元素,否则报警告。
示例
<script setup lang="ts"></script>
<template>
<div id="App">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</template>
<style lang="scss" scoped>
.fade-enter-active {
transition: all 1s ease-out;
}
.fade-leave-active {
transition: all 1s ease-out;
}
.fade-enter-from,
.fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
组件其他属性
初渲染过渡
如果你想在某个节点初次渲染时应用一个过渡效果,你可以添加 appear prop:
<Transition appear>
...
</Transition>
多子元素使用v-else-if过渡
除了通过 v-if / v-show 切换一个元素,我们也可以通过 v-if / v-else / v-else-if 在几个组件间进行切换,只要确保任一时刻只会有一个元素被渲染即可:
<Transition>
<button v-if="docState === 'saved'">Edit</button>
<button v-else-if="docState === 'edited'">Save</button>
<button v-else-if="docState === 'editing'">Cancel</button>
</Transition>
过渡模式
mode 属性可以指定组件实现动画时行为顺序。mode值为"out-in"时表示 先执行离开动画,再执行进入动画。mode值为"in-out"则相反。
<Transition mode="out-in">
...
</Transition>
TransitionGroup组件
<TransitionGroup> 是一个内置组件,用于对 v-for 列表中的元素或组件的插入、移除和顺序改变添加动画效果。
和 <Transition> 的区别
<TransitionGroup> 支持和 <Transition> 基本相同的 props、CSS 过渡 class 和 JavaScript 钩子监听器,但有以下几点区别:
-
默认情况下,它不会渲染一个容器元素。但你可以通过传入 tag prop 来指定一个元素作为容器元素来渲染。
-
过渡模式在这里不可用,因为我们不再是在互斥的元素之间进行切换。
-
列表中的每个元素都必须有一个独一无二的 key attribute。
-
CSS 过渡 class 会被应用在列表内的元素上,而不是容器元素上。
移动动画
可以添加list-move类实现平稳移动效果。为了确保要离开的元素从布局中删除,可以在list-leave-active时手动改变布局方式移除。
.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;
}
示例
<template>
<div class="container">
<h1>TransitionGroup</h1>
<a-input v-model:value="inputValue" placeholder="请输入" />
<a-button @click="add">添加</a-button>
<a-button @click="del">随机删除</a-button>
<a-button @click="emptyHandler">置空</a-button>
<TransitionGroup name="list" tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</TransitionGroup>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, toRefs, onBeforeMount, onMounted } from "vue";
const items = reactive<Array<number | string>>([1, 2, 3]);
const inputValue = ref("123");
// add
const add = () => {
console.log(inputValue, inputValue.value);
if (inputValue.value !== "") {
items.push(inputValue.value);
inputValue.value = "";
}
};
// del
const del = () => {
let randomNum = Math.floor(Math.random() * (items.length - 1));
items.splice(randomNum, 1);
};
// emptyHandler
const emptyHandler = () => {
items.splice(0);
};
</script>
<style lang="scss" scoped>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(10px);
}
/* 确保将离开的元素从布局流中删除
以便能够正确地计算移动的动画。 */
.list-leave-active {
position: absolute;
}
</style>
结语
结束了。过渡状态一般会在页面模块级改变时使用,例如路由过渡效果;vue的过渡状态是针对元素设计的。列表过渡在数据实时改变时可以使用。