过渡
Vue提供两个内置组件,可以帮助处理响应状态变化时的过渡和动画
<Transition>
在元素或组件进入和离开DOM时应用动画。这在本页上有介绍。<TransitionGroup>
在元素或组件被插入到、从v-for
列表中移除或在其内部移动时应用动画。这将在下一章中介绍。
除了这两个组件之外,我们还可以使用其他技术,如切换CSS类或通过样式绑定实现状态驱动的动画,在Vue中应用动画。这些额外的技术将在动画技巧章节中介绍。
<Transition> 组件
<Transition>
是一个内置组件:这意味着它可以在任何组件的模板中使用,而无需注册。它可以用于对其默认插槽传入的元素或组件应用进入和离开动画。进入或离开可以由以下之一触发
- 通过
v-if
进行条件渲染 - 通过
v-show
进行条件显示 - 通过
<component>
特殊元素动态切换组件 - 更改特殊的
key
属性
这是一个最基本使用示例
模板
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
CSS
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
hello
提示
<Transition>
只支持单个元素或组件作为其插槽内容。如果内容是组件,该组件也必须只有一个根元素。
当 <Transition>
组件中的元素被插入或移除时,会发生以下情况
Vue会自动检查目标元素是否应用了CSS过渡或动画。如果应用了,将添加/移除一系列CSS过渡类,时机适当。
如果有监听器监听JavaScript钩子,则这些钩子将在适当的时机被调用。
如果没有检测到CSS过渡/动画,并且没有提供JavaScript钩子,则插入和/或删除的DOM操作将在浏览器的下一个动画帧上执行。
基于CSS的过渡
过渡类
共有六个类用于进入/离开过渡。
v-enter-from
:进入的起始状态。在元素插入前添加,元素插入后一帧移除。v-enter-active
:进入的活跃状态。在整个进入阶段应用。在元素插入前添加,在过渡/动画完成后移除。这个类可以用来定义进入过渡的持续时间、延迟和缓动曲线。v-enter-to
:进入的结束状态。在元素插入后一帧添加(同时移除v-enter-from
),在过渡/动画完成后移除。v-leave-from
:离开的起始状态。在离开过渡触发时立即添加,一帧后移除。v-leave-active
:离开的活跃状态。在整个离开阶段应用。在离开过渡触发时立即添加,在过渡/动画完成后移除。这个类可以用来定义离开过渡的持续时间、延迟和缓动曲线。v-leave-to
:离开的结束状态。在离开过渡触发后一帧添加(同时移除v-leave-from
),在过渡/动画完成后移除。
v-enter-active
和v-leave-active
允许我们为进入/离开过渡指定不同的缓动曲线,以下章节将给出一个示例。
命名过渡
可以通过name
属性命名一个过渡。
模板
<Transition name="fade">
...
</Transition>
对于命名过渡,其过渡类将以其名称为前缀而不是v
。例如,上述过渡应用的是fade-enter-active
而不是v-enter-active
。fade过渡的CSS应如下所示
CSS
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
CSS过渡
<Transition>
通常与原生CSS过渡结合使用,如上述基本示例所示。CSS的transition
属性是一个简写,允许我们指定过渡的多个方面,包括应动画化的属性、过渡的持续时间以及缓动曲线。
以下是一个更高级的示例,它转换多个属性,为进入和离开指定不同的持续时间缓动曲线。
模板
<Transition name="slide-fade">
<p v-if="show">hello</p>
</Transition>
CSS
/*
Enter and leave animations can use different
durations and timing functions.
*/
.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;
}
hello
CSS动画
原生CSS动画的应用方式与CSS过渡相同,区别在于*-enter-from
不是在元素插入后立即移除,而是在animationend
事件发生时移除。
对于大多数CSS动画,我们可以在*-enter-active
和*-leave-active
类下简单声明它们。以下是一个示例
模板
<Transition name="bounce">
<p v-if="show" style="text-align: center;">
Hello here is some bouncy text!
</p>
</Transition>
CSS
.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);
}
}
你好,这里有一些弹跳文本!
自定义过渡类
您还可以通过向<Transition>
传递以下属性来指定自定义过渡类
enter-from-class
enter-active-class
enter-to-class
leave-from-class
leave-active-class
leave-to-class
这些将覆盖常规类名。当您想将Vue的过渡系统与现有的CSS动画库(如Animate.css)结合使用时,这特别有用。
模板
<!-- assuming Animate.css is included on the page -->
<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>
同时使用过渡和动画
Vue需要附加事件监听器以了解何时过渡结束。它可以是被应用的CSS规则的类型决定的transitionend
或animationend
。如果您只使用其中之一,Vue可以自动检测正确的类型。
但是,在某些情况下,您可能希望在同一个元素上同时使用这两种效果,例如,当Vue触发CSS动画时,在悬停时添加CSS过渡效果。在这些情况下,您必须通过传递具有值为animation
或transition
的type
属性来显式声明Vue应该关注的类型。
模板
<Transition type="animation">...</Transition>
嵌套过渡和显式过渡持续时间
尽管过渡类只应用于<Transition>
中的直接子元素,但我们可以使用嵌套CSS选择器来过渡嵌套元素
模板
<Transition name="nested">
<div v-if="show" class="outer">
<div class="inner">
Hello
</div>
</div>
</Transition>
CSS
/* rules that target nested elements */
.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;
}
/* ... other necessary CSS omitted */
我们甚至可以在进入时为嵌套元素添加过渡延迟,这会创建一个交错进入动画序列
CSS
/* delay enter of nested element for staggered effect */
.nested-enter-active .inner {
transition-delay: 0.25s;
}
然而,这会引发一个小问题。默认情况下,<Transition>
组件尝试通过监听根过渡元素的第一个transitionend
或animationend
事件来自动确定何时过渡完成。对于嵌套过渡,所需的操作应该是等待所有内部元素的过渡都完成。
在这种情况下,您可以使用<transition>
组件上的duration
属性(以毫秒为单位)指定显式过渡持续时间。总持续时间应等于延迟加上内部元素的过渡持续时间
模板
<Transition :duration="550">...</Transition>
你好
如果需要,您还可以使用对象指定进入和离开的持续时间
模板
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>
性能考虑因素
您可能已经注意到上述动画大多使用transform
和opacity
等属性。这些属性易于动画化,因为
它们在动画期间不影响文档布局,因此它们不会在每一帧上触发昂贵的CSS布局计算。
大多数现代浏览器在动画
transform
时可以利用GPU硬件加速。
相比之下,height
或margin
等属性将触发CSS布局,因此它们动画化起来成本更高,应谨慎使用。
JavaScript钩子
您可以通过监听<Transition>
组件上的事件来使用JavaScript挂钩到过渡过程
html
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
js
// called before the element is inserted into the DOM.
// use this to set the "enter-from" state of the element
function onBeforeEnter(el) {}
// called one frame after the element is inserted.
// use this to start the entering animation.
function onEnter(el, done) {
// call the done callback to indicate transition end
// optional if used in combination with CSS
done()
}
// called when the enter transition has finished.
function onAfterEnter(el) {}
// called when the enter transition is cancelled before completion.
function onEnterCancelled(el) {}
// called before the leave hook.
// Most of the time, you should just use the leave hook
function onBeforeLeave(el) {}
// called when the leave transition starts.
// use this to start the leaving animation.
function onLeave(el, done) {
// call the done callback to indicate transition end
// optional if used in combination with CSS
done()
}
// called when the leave transition has finished and the
// element has been removed from the DOM.
function onAfterLeave(el) {}
// only available with v-show transitions
function onLeaveCancelled(el) {}
这些钩子可以与CSS过渡/动画结合使用,或者单独使用。
在使用仅JavaScript的过渡时,通常添加:css="false"
属性是个好主意。这会明确告诉Vue跳过自动CSS过渡检测。除了性能略高外,这还可以防止CSS规则意外干扰过渡。
模板
<Transition
...
:css="false"
>
...
</Transition>
使用:css="false"
时,我们也完全负责控制过渡何时结束。在这种情况下,对于@enter
和@leave
钩子,需要提供done
回调。否则,钩子将同步调用,并且过渡将立即完成。
以下是一个使用GSAP库执行动画的示例。当然,您可以使用任何其他动画库,例如Anime.js或Motion One
可重用过渡
可以通过Vue的组件系统重用过渡。要创建可重用的过渡,我们可以创建一个组件,该组件包装了<Transition>
组件,并将槽内容传递下去。
vue
<!-- MyTransition.vue -->
<script>
// JavaScript hooks logic...
</script>
<template>
<!-- wrap the built-in Transition component -->
<Transition
name="my-transition"
@enter="onEnter"
@leave="onLeave">
<slot></slot> <!-- pass down slot content -->
</Transition>
</template>
<style>
/*
Necessary CSS...
Note: avoid using <style scoped> here since it
does not apply to slot content.
*/
</style>
现在MyTransition
可以像内置版本一样导入和使用。
模板
<MyTransition>
<div v-if="show">Hello</div>
</MyTransition>
出现在过渡
如果您还希望在节点的初始渲染时应用过渡,可以添加appear
属性。
模板
<Transition appear>
...
</Transition>
元素间的过渡
除了使用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>
点击循环状态
过渡模式
在上一个示例中,进入和离开元素同时进行动画,我们必须将它们设置为position: absolute
以避免在DOM中同时存在两个元素时的布局问题。
但是,在某些情况下这不是一个选项,或者根本不是我们想要的操作。我们可能希望先对离开元素进行动画,然后只有在离开动画完成后才将进入元素插入。手动编排这样的动画将非常复杂——幸运的是,我们可以通过传递一个mode
属性给<Transition>
来启用这种行为。
模板
<Transition mode="out-in">
...
</Transition>
以下是之前示例中带有mode="out-in"
的演示。
点击循环状态
<Transition>
还支持mode="in-out"
,尽管它使用得较少。
组件间的过渡
<Transition>
也可以用于动态组件周围。
模板
<Transition name="fade" mode="out-in">
<component :is="activeComponent"></component>
</Transition>
组件A
动态过渡
<Transition>
的name
等属性也可以是动态的!这允许我们根据状态变化动态应用不同的过渡。
模板
<Transition :name="transitionName">
<!-- ... -->
</Transition>
当您使用Vue的过渡类约定定义了CSS过渡/动画,并希望在这些过渡之间切换时,这可能会很有用。
您还可以根据组件的当前状态,在JavaScript过渡钩子中应用不同的行为。最后,创建动态过渡的终极方法是使用可复用过渡组件,这些组件接受props来改变要使用的过渡的性质。这可能听起来有点陈词滥调,但真正的限制实际上只是您的想象力。
具有键属性(Key Attribute)的过渡
有时您需要强制重新渲染DOM元素,以便过渡发生。
以这个计数器组件为例
vue
<script setup>
import { ref } from 'vue';
const count = ref(0);
setInterval(() => count.value++, 1000);
</script>
<template>
<Transition>
<span :key="count">{{ count }}</span>
</Transition>
</template>
如果我们排除了key
属性,则只会更新文本节点,因此不会发生过渡。然而,当存在key
属性时,Vue知道每当count
发生变化时,都会创建一个新的span
元素,因此Transition
组件有两个不同的元素可以过渡。
相关