多个元素或组件的过渡/动画效果

在前面的演示代码中,使用 transition 组件实现过渡/动画效果时,都是只给 transition 组件内的一个 <div> 元素套用了动画效果。在 Vue 中,同样支持给多个元素添加过渡/动画效果。下面还是以之前的过渡/动画演示代码为例,如示例代码 5-6-1 所示。

示例代码 5-6-1 transition 组件多个元素的过渡/动画效果1
.fade-enter,.fade-leave-to {
    opacity: 0
}
.fade-enter-active,.fade-leave-active {
    transition: opacity 2s
}
<div id="app">
    <button @click="clickCallback">切换</button>
    <transition name="fade">
        <div v-if="show">Hello!</div>
        <div v-else>World!</div>
    </transition>
</div>

从上面的代码可知,在 transition 组件中定义了两个 <div> 子元素,并分别使用 v-ifv-else 来控制显示和隐藏,同时将 fade 的过渡效果套用到这两个子元素中。但是,当运行代码时,并没有出现:Hello! 显示、World!隐藏或者 Hello!隐藏、World!显示这些效果。这是为什么呢?

当有相同标签名的元素切换时,正如示例代码中的两个子元素都采用的是 <div>Vue 为了效率,只会替换相同标签内部的内容,而不会整体替换,需要通过 key 属性设置唯一的值来标记,以让 Vue 区分它们。所以,需要给每个 div 设置一个唯一的 key 值,代码如下:

<transition name="fade">
    <div v-if="show" key="a">Hello!</div>
    <div v-else key="b">World!</div>
</transition>

再次运行这段代码,就可以看到动画效果,但是目前的动画效果还不是最完美的效果。在 Hello!显示、World!隐藏或者 Hello!隐藏、World!显示时,两个元素会发生重叠,也就是说一个 <div> 元素在执行离开过渡,同时另一个 <div> 元素在执行进入过渡,这是 transition 组件的默认行为:进入和离开同时发生。针对这个问题,transition 组件提供了过渡模式 mode 的设置项:

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。

  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。

可以尝试将 mode 设置成 out-in 来看看效果,代码如下:

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

通过上面的配置,再次运行动画代码,就不会再发生重叠现象。<transition> 不仅可以为多个 <div> 等原生的 HTML 元素添加过渡/动画效果,对于多个不同的自定义组件也可以使用。另外,切换组件除了使用 v-if 或者 v-show 之外,也可以使用动态组件 <component> 来实现不同组件的替换,如示例代码 5-6-2 所示。

示例代码 5-6-2 transition 多个组件的过渡/动画效果2
<style type="text/css">
.component-fade-enter-active, .component-fade-leave-active {
    transition: opacity .3s ease;
}
.component-fade-enter-from, .component-fade-leave-to {
    opacity: 0;
}
</style>
...
<div id="app">
    <button @click="clickCallback">切换</button>
    <transition name="component-fade" mode="out-in">
        <component :is="view"></component>
    </transition>
</div>
...
Vue.createApp({
    data() {
        return {
            view: 'a',
            count: 0
        }
    },
    components: {
        'a': { // 子组件A
            template: '<div>Component A</div>'
        },
        'b': {// 子组件B
            template: '<div>Component B</div>'
        }
    },
    methods:{
        clickCallback(){
            if (this.count % 2 == 1) {
                this.view = 'a'
            } else {
                this.view = 'b'
            }
            this.count++
        }
    }
}).mount("#app")

上面的代码中,<transition> 组件中只包含一个 <component> 组件,但是可以通过 v-bind 指令加 is 来实现不同组件的替换,并且应用上了过渡效果,读者可以在浏览器中运行体验。