跳转到内容

过渡

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> 组件中的元素被插入或移除时,会发生以下情况

  1. Vue会自动检查目标元素是否应用了CSS过渡或动画。如果应用了,将添加/移除一系列CSS过渡类,时机适当。

  2. 如果有监听器监听JavaScript钩子,则这些钩子将在适当的时机被调用。

  3. 如果没有检测到CSS过渡/动画,并且没有提供JavaScript钩子,则插入和/或删除的DOM操作将在浏览器的下一个动画帧上执行。

基于CSS的过渡

过渡类

共有六个类用于进入/离开过渡。

Transition Diagram

  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),在过渡/动画完成后移除。

v-enter-activev-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规则的类型决定的transitionendanimationend。如果您只使用其中之一,Vue可以自动检测正确的类型。

但是,在某些情况下,您可能希望在同一个元素上同时使用这两种效果,例如,当Vue触发CSS动画时,在悬停时添加CSS过渡效果。在这些情况下,您必须通过传递具有值为animationtransitiontype属性来显式声明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>组件尝试通过监听根过渡元素的第一个transitionendanimationend事件来自动确定何时过渡完成。对于嵌套过渡,所需的操作应该是等待所有内部元素的过渡都完成。

在这种情况下,您可以使用<transition>组件上的duration属性(以毫秒为单位)指定显式过渡持续时间。总持续时间应等于延迟加上内部元素的过渡持续时间

模板
<Transition :duration="550">...</Transition>
你好

在Playground中尝试

如果需要,您还可以使用对象指定进入和离开的持续时间

模板
<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>

性能考虑因素

您可能已经注意到上述动画大多使用transformopacity等属性。这些属性易于动画化,因为

  1. 它们在动画期间不影响文档布局,因此它们不会在每一帧上触发昂贵的CSS布局计算。

  2. 大多数现代浏览器在动画transform时可以利用GPU硬件加速。

相比之下,heightmargin等属性将触发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) {}
js
export default {
  // ...
  methods: {
    // called before the element is inserted into the DOM.
    // use this to set the "enter-from" state of the element
    onBeforeEnter(el) {},

    // called one frame after the element is inserted.
    // use this to start the animation.
    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.
    onAfterEnter(el) {},

    // called when the enter transition is cancelled before completion.
    onEnterCancelled(el) {},

    // called before the leave hook.
    // Most of the time, you should just use the leave hook.
    onBeforeLeave(el) {},

    // called when the leave transition starts.
    // use this to start the leaving animation.
    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.
    onAfterLeave(el) {},

    // only available with v-show transitions
    onLeaveCancelled(el) {}
  }
}

这些钩子可以与CSS过渡/动画结合使用,或者单独使用。

在使用仅JavaScript的过渡时,通常添加:css="false"属性是个好主意。这会明确告诉Vue跳过自动CSS过渡检测。除了性能略高外,这还可以防止CSS规则意外干扰过渡。

模板
<Transition
  ...
  :css="false"
>
  ...
</Transition>

使用:css="false"时,我们也完全负责控制过渡何时结束。在这种情况下,对于@enter@leave钩子,需要提供done回调。否则,钩子将同步调用,并且过渡将立即完成。

以下是一个使用GSAP库执行动画的示例。当然,您可以使用任何其他动画库,例如Anime.jsMotion 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>
点击循环状态

在Playground中尝试

过渡模式

在上一个示例中,进入和离开元素同时进行动画,我们必须将它们设置为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>
vue
<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>

如果我们排除了key属性,则只会更新文本节点,因此不会发生过渡。然而,当存在key属性时,Vue知道每当count发生变化时,都会创建一个新的span元素,因此Transition组件有两个不同的元素可以过渡。


相关

过渡已加载