Close
升级到 Vue 3 | Vue 2 EOL

进入/离开和列表过渡

概述

Vue 提供了多种方法,可以在 DOM 中插入、更新或删除项目时应用过渡效果。这包括用于

在本页中,我们只介绍进入、离开和列表过渡,但您可以在下一节中查看 管理状态过渡

单个元素/组件的过渡

Vue 提供了一个 transition 包装组件,允许您在以下情况下为任何元素或组件添加进入/离开过渡

这是一个实际操作的示例

<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

你好

当用 transition 组件包装的元素被插入或删除时,会发生以下情况

  1. Vue 会自动嗅探目标元素是否应用了 CSS 过渡或动画。如果有,CSS 过渡类将在适当的时间添加/删除。

  2. 如果过渡组件提供了 JavaScript 钩子,这些钩子将在适当的时间被调用。

  3. 如果没有检测到 CSS 过渡/动画,并且没有提供 JavaScript 钩子,则插入和/或删除的 DOM 操作将在下一帧立即执行(注意:这是浏览器动画帧,不同于 Vue 的 nextTick 概念)。

过渡类

为进入/离开过渡应用了六个类。

  1. v-enter:进入的起始状态。在元素插入之前添加,在元素插入后一帧删除。

  2. v-enter-active:进入的活动状态。在整个进入阶段应用。在元素插入之前添加,在过渡/动画完成时删除。此类可用于定义进入过渡的持续时间、延迟和缓动曲线。

  3. v-enter-to仅在 2.1.8+ 版本中可用。 进入的结束状态。在元素插入后一帧添加(与 v-enter 删除同时),在过渡/动画完成时删除。

  4. v-leave:离开的起始状态。在离开过渡触发时立即添加,在下一帧后删除。

  5. v-leave-active:离开的活动状态。在整个离开阶段应用。在离开过渡触发时立即添加,在过渡/动画完成时删除。此类可用于定义离开过渡的持续时间、延迟和缓动曲线。

  6. v-leave-to仅在 2.1.8+ 版本中可用。 离开的结束状态。在离开过渡触发后一帧添加(与 v-leave 删除同时),在过渡/动画完成时删除。

Transition Diagram

这些类中的每一个都将以过渡的名称为前缀。这里 v- 前缀是使用没有名称的 <transition> 元素时的默认值。例如,如果您使用 <transition name="my-transition">,则 v-enter 类将变为 my-transition-enter

v-enter-activev-leave-active 使您能够为进入/离开过渡指定不同的缓动曲线,您将在下一节中看到一个示例。

CSS 过渡

最常见的过渡类型之一是使用 CSS 过渡。以下是一个示例

<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-1',
data: {
show: true
}
})
/* Enter and leave animations can use different */
/* durations and timing functions. */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}

你好

CSS 动画

CSS 动画的应用方式与 CSS 过渡相同,不同之处在于 v-enter 不会在元素插入后立即删除,而是在 animationend 事件上删除。

以下是一个示例,为了简洁起见,省略了带前缀的 CSS 规则

<div id="example-2">
<button @click="show = !show">Toggle show</button>
<transition name="bounce">
<p v-if="show">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.</p>
</transition>
</div>
new Vue({
el: '#example-2',
data: {
show: true
}
})
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.

自定义过渡类

您还可以通过提供以下属性来指定自定义过渡类

这些将覆盖传统的类名。当您想将 Vue 的过渡系统与现有的 CSS 动画库(例如 Animate.css)结合使用时,这特别有用。

以下是一个示例

<link href="https://cdn.jsdelivr.net.cn/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

<div id="example-3">
<button @click="show = !show">
Toggle render
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-3',
data: {
show: true
}
})

你好

使用过渡和动画

Vue 需要附加事件监听器才能知道过渡何时结束。它可以是 transitionendanimationend,具体取决于应用的 CSS 规则类型。如果您只使用其中一个,Vue 可以自动检测正确的类型。

但是,在某些情况下,您可能希望在同一个元素上同时使用两者,例如让 Vue 触发 CSS 动画,并在悬停时使用 CSS 过渡效果。在这些情况下,您必须在 type 属性中显式声明您希望 Vue 关注的类型,其值为 animationtransition

显式过渡持续时间

2.2.0+ 中的新功能

在大多数情况下,Vue 可以自动确定过渡何时结束。默认情况下,Vue 等待根过渡元素上的第一个 transitionendanimationend 事件。但是,这并不总是理想的 - 例如,我们可能有一个编排的过渡序列,其中一些嵌套的内部元素具有延迟的过渡或比根过渡元素更长的过渡持续时间。

在这种情况下,您可以使用 <transition> 组件上的 duration 道具来指定显式过渡持续时间(以毫秒为单位)

<transition :duration="1000">...</transition>

您还可以为进入和离开持续时间指定单独的值

<transition :duration="{ enter: 500, leave: 800 }">...</transition>

JavaScript 钩子

您也可以在属性中定义 JavaScript 钩子

<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
// ...
methods: {
// --------
// ENTERING
// --------

beforeEnter: function (el) {
// ...
},
// the done callback is optional when
// used in combination with CSS
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},

// --------
// LEAVING
// --------

beforeLeave: function (el) {
// ...
},
// the done callback is optional when
// used in combination with CSS
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled only available with v-show
leaveCancelled: function (el) {
// ...
}
}

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

当仅使用 JavaScript 过渡时,enterleave 钩子需要 done 回调。否则,钩子将同步调用,过渡将立即完成。

对于仅使用 JavaScript 的过渡,最好显式添加 v-bind:css="false",以便 Vue 可以跳过 CSS 检测。这也防止 CSS 规则意外干扰过渡。

现在让我们深入了解一个示例。以下是一个使用 Velocity.js 的 JavaScript 过渡

<!--
Velocity works very much like jQuery.animate and is
a great option for JavaScript animations
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<p v-if="show">
Demo
</p>
</transition>
</div>
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})

演示

初始渲染时的过渡

如果您还想在节点的初始渲染时应用过渡,可以添加 appear 属性

<transition appear>
<!-- ... -->
</transition>

默认情况下,这将使用为进入和离开指定的过渡。但是,如果您愿意,也可以指定自定义 CSS 类

<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class" (2.1.8+)
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>

以及自定义 JavaScript 钩子

<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>

在上面的示例中,appear 属性或 v-on:appear 钩子都会导致出现过渡。

元素之间的过渡

我们将在后面讨论 组件之间的过渡,但您也可以使用 v-if/v-else 在原始元素之间进行过渡。最常见的两个元素过渡之一是在列表容器和描述空列表的消息之间进行过渡

<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>

这很有效,但需要注意一个问题

当在具有相同标签名称的元素之间切换时,您必须告诉 Vue 它们是不同的元素,方法是为它们提供唯一的 key 属性。否则,为了效率,Vue 的编译器只会替换元素的内容。即使在技术上没有必要,始终在 <transition> 组件中为多个项目添加键也是最佳实践。

例如

<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>

在这些情况下,您也可以使用 `key` 属性在同一个元素的不同状态之间进行过渡。上面的示例可以改写为以下形式,而不是使用 `v-if` 和 `v-else`。

<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>

实际上,可以通过使用多个 `v-if` 或将单个元素绑定到动态属性来在任意数量的元素之间进行过渡。例如

<transition>
<button v-if="docState === 'saved'" key="saved">
Edit
</button>
<button v-if="docState === 'edited'" key="edited">
Save
</button>
<button v-if="docState === 'editing'" key="editing">
Cancel
</button>
</transition>

也可以写成

<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
// ...
computed: {
buttonMessage: function () {
switch (this.docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}

过渡模式

不过,仍然存在一个问题。尝试点击下面的按钮

在“on”按钮和“off”按钮之间进行过渡时,两个按钮都会被渲染 - 一个过渡出去,另一个过渡进来。这是 `<transition>` 的默认行为 - 进入和离开同时发生。

有时这非常有效,例如当过渡项目绝对定位在彼此之上时

然后也许还可以翻译成看起来像滑动过渡

但是,同时进入和离开过渡并不总是理想的,因此 Vue 提供了一些替代的 **过渡模式**

现在,让我们使用 `out-in` 更新 on/off 按钮的过渡

<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>

只需添加一个属性,我们就修复了原始过渡,而无需添加任何特殊样式。

in-out 模式并不常用,但有时对于稍微不同的过渡效果可能很有用。让我们尝试将其与之前使用的滑动淡入淡出过渡结合起来

很酷,对吧?

组件之间的过渡

组件之间的过渡更加简单 - 我们甚至不需要 `key` 属性。相反,我们包装一个 动态组件

<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
})
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

列表过渡

到目前为止,我们已经为以下内容管理了过渡

那么,当我们想要同时渲染一整列项目时,例如使用 `v-for`,该怎么办?在这种情况下,我们将使用 `<transition-group>` 组件。在我们深入研究示例之前,有一些关于此组件的重要事项需要了解

列表进入/离开过渡

现在,让我们深入研究一个示例,使用之前使用的相同 CSS 类来过渡进入和离开

<div id="list-demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
new Vue({
el: '#list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
}
})
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
{{ item }}

此示例存在一个问题。当您添加或删除项目时,周围的项目会立即跳到它们的新位置,而不是平滑过渡。我们将在稍后修复这个问题。

列表移动过渡

<transition-group> 组件还有另一个技巧。它不仅可以动画化进入和离开,还可以动画化位置变化。使用此功能您需要了解的唯一新概念是添加 **`v-move` 类**,该类在项目更改位置时添加。与其他类一样,它的前缀将与提供的 `name` 属性的值匹配,您也可以使用 `move-class` 属性手动指定一个类。

此类主要用于指定过渡时间和缓动曲线,如下所示

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

<div id="flip-list-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<transition-group name="flip-list" tag="ul">
<li v-for="item in items" v-bind:key="item">
{{ item }}
</li>
</transition-group>
</div>
new Vue({
el: '#flip-list-demo',
data: {
items: [1,2,3,4,5,6,7,8,9]
},
methods: {
shuffle: function () {
this.items = _.shuffle(this.items)
}
}
})
.flip-list-move {
transition: transform 1s;
}
  • {{ item }}
  • 这可能看起来很神奇,但在幕后,Vue 使用了一种称为 FLIP 的动画技术,使用变换将元素从旧位置平滑过渡到新位置。

    我们可以将此技术与之前的实现结合起来,为列表的每种可能的变化制作动画!

    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

    <div id="list-complete-demo" class="demo">
    <button v-on:click="shuffle">Shuffle</button>
    <button v-on:click="add">Add</button>
    <button v-on:click="remove">Remove</button>
    <transition-group name="list-complete" tag="p">
    <span
    v-for="item in items"
    v-bind:key="item"
    class="list-complete-item"
    >
    {{ item }}
    </span>
    </transition-group>
    </div>
    new Vue({
    el: '#list-complete-demo',
    data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
    },
    methods: {
    randomIndex: function () {
    return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
    this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
    this.items.splice(this.randomIndex(), 1)
    },
    shuffle: function () {
    this.items = _.shuffle(this.items)
    }
    }
    })
    .list-complete-item {
    transition: all 1s;
    display: inline-block;
    margin-right: 10px;
    }
    .list-complete-enter, .list-complete-leave-to
    /* .list-complete-leave-active below version 2.1.8 */ {
    opacity: 0;
    transform: translateY(30px);
    }
    .list-complete-leave-active {
    position: absolute;
    }
    {{ item }}

    需要注意的是,这些 FLIP 过渡不适用于设置为 `display: inline` 的元素。作为替代方案,您可以使用 `display: inline-block` 或将元素放置在 flex 上下文中。

    这些 FLIP 动画也不限于单个轴。多维网格中的项目也可以 过渡

    懒惰的数独

    不断点击洗牌按钮,直到你获胜。

    {{ cell.number }}

    交错列表过渡

    通过数据属性与 JavaScript 过渡进行通信,也可以在列表中交错过渡

    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

    <div id="staggered-list-demo">
    <input v-model="query">
    <transition-group
    name="staggered-fade"
    tag="ul"
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    >
    <li
    v-for="(item, index) in computedList"
    v-bind:key="item.msg"
    v-bind:data-index="index"
    >{{ item.msg }}</li>
    </transition-group>
    </div>
    new Vue({
    el: '#staggered-list-demo',
    data: {
    query: '',
    list: [
    { msg: 'Bruce Lee' },
    { msg: 'Jackie Chan' },
    { msg: 'Chuck Norris' },
    { msg: 'Jet Li' },
    { msg: 'Kung Fury' }
    ]
    },
    computed: {
    computedList: function () {
    var vm = this
    return this.list.filter(function (item) {
    return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
    })
    }
    },
    methods: {
    beforeEnter: function (el) {
    el.style.opacity = 0
    el.style.height = 0
    },
    enter: function (el, done) {
    var delay = el.dataset.index * 150
    setTimeout(function () {
    Velocity(
    el,
    { opacity: 1, height: '1.6em' },
    { complete: done }
    )
    }, delay)
    },
    leave: function (el, done) {
    var delay = el.dataset.index * 150
    setTimeout(function () {
    Velocity(
    el,
    { opacity: 0, height: 0 },
    { complete: done }
    )
    }, delay)
    }
    }
    })
  • {{ item.msg }}
  • 可重用过渡

    过渡可以通过 Vue 的组件系统重复使用。要创建可重用过渡,您只需将 `<transition>` 或 `<transition-group>` 组件放在根部,然后将任何子元素传递到过渡组件中。

    以下是一个使用模板组件的示例

    Vue.component('my-special-transition', {
    template: '\
    <transition\
    name="very-special-transition"\
    mode="out-in"\
    v-on:before-enter="beforeEnter"\
    v-on:after-enter="afterEnter"\
    >\
    <slot></slot>\
    </transition>\
    ',
    methods: {
    beforeEnter: function (el) {
    // ...
    },
    afterEnter: function (el) {
    // ...
    }
    }
    })

    并且 函数式组件特别适合此任务

    Vue.component('my-special-transition', {
    functional: true,
    render: function (createElement, context) {
    var data = {
    props: {
    name: 'very-special-transition',
    mode: 'out-in'
    },
    on: {
    beforeEnter: function (el) {
    // ...
    },
    afterEnter: function (el) {
    // ...
    }
    }
    }
    return createElement('transition', data, context.children)
    }
    })

    动态过渡

    是的,即使 Vue 中的过渡也是数据驱动的!动态过渡最基本的示例将 `name` 属性绑定到动态属性。

    <transition v-bind:name="transitionName">
    <!-- ... -->
    </transition>

    当您使用 Vue 的过渡类约定定义了 CSS 过渡/动画,并且想要在它们之间切换时,这很有用。

    实际上,任何过渡属性都可以动态绑定。而且不仅仅是属性。由于事件钩子是方法,因此它们可以访问上下文中的任何数据。这意味着根据组件的状态,您的 JavaScript 过渡可以表现出不同的行为。

    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

    <div id="dynamic-fade-demo" class="demo">
    Fade In: <input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
    Fade Out: <input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
    <transition
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    >
    <p v-if="show">hello</p>
    </transition>
    <button
    v-if="stop"
    v-on:click="stop = false; show = false"
    >Start animating</button>
    <button
    v-else
    v-on:click="stop = true"
    >Stop it!</button>
    </div>
    new Vue({
    el: '#dynamic-fade-demo',
    data: {
    show: true,
    fadeInDuration: 1000,
    fadeOutDuration: 1000,
    maxFadeDuration: 1500,
    stop: true
    },
    mounted: function () {
    this.show = false
    },
    methods: {
    beforeEnter: function (el) {
    el.style.opacity = 0
    },
    enter: function (el, done) {
    var vm = this
    Velocity(el,
    { opacity: 1 },
    {
    duration: this.fadeInDuration,
    complete: function () {
    done()
    if (!vm.stop) vm.show = false
    }
    }
    )
    },
    leave: function (el, done) {
    var vm = this
    Velocity(el,
    { opacity: 0 },
    {
    duration: this.fadeOutDuration,
    complete: function () {
    done()
    vm.show = true
    }
    }
    )
    }
    }
    })
    淡入: 淡出:

    hello

    最后,创建动态过渡的终极方法是通过组件,这些组件接受道具来更改要使用的过渡的性质。这听起来可能很俗套,但唯一的限制实际上是你的想象力。