通过<RouterView/>来切换页面组件时,transition如何生效?

2 阅读3分钟

场景

在使用Vue提供的transition组件来实现页面切换时的过渡效果时,直接使用了transition来包裹路由,结果发现了一个问题,新页面进入时的动画效果成功实现了,而旧页面离开的动画却失效了。

子页面1:

<template>
    <div class="page1">
        page1
    </div>
</template>

<style scoped>
.page1 {
    width: 100%;
    height: 100%;
    border: 1px solid #000;
    background-color: pink;
    text-align: center;
    font-size: 50px;
}
</style>

子页面2:

<template>
    <div class="page2">
        page2
    </div>
</template>

<style scoped>
.page2 {
    width: 100%;
    height: 100%;
    background-color: blue;
    text-align: center;
    font-size: 50px;
}
</style>

主页面:

<template>
<div class="container">
      <div class="tabs">
    <router-link to="/page1">page1</router-link>
    <router-link to="/page2">page2</router-link>
  </div>
  <div class="page">
      <transition name="fade" mode="out-in">
          </router-view>
      </transition>
  </div>
</div>
</template>

<style>
.container {
  margin: 0 auto;
  width: 800px;
  height: 600px;
  border: 1px solid #000;
}
.page {
  width: 100%;
  height: calc(100% - 60px);
}
.tabs {
  height: 40px;
  width: 200px;
  margin: 0 auto;
  background: green;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 20px;
  margin-bottom: 20px;
}
a {
  color: #fff;
  font-size: 20px;
}
.fade-enter-active {
  transition: all 0.5s ease-in-out;
}
.fade-enter-from {
  opacity: 0;
  transform: translateX(100%);
}
.fade-enter-to {
  opacity: 1;
  transform: translateX(0);
}
.fade-leave-active {
  transition: all 0.5s ease-in-out;
}
.fade-leave-from {
  opacity: 1;
  transform: translateX(0);
}
.fade-leave-to {
  opacity: 0;
  transform: translateX(-100%);
}
</style>

页面效果如下: 切换页面 .fade-enter相关CSS成功执行,.fade-leave相关CSS执行失败.(如图)

解决方法

后来我从别人那获取到了解决方法:用 RouterView 包裹 Transition 配合 Component 实现过渡效果。

<template>
    <router-view v-slot="{ Component }">
      <transition>
        <component :is="Component" />
      </transition>
    </router-view>
<template/>

改为这样子后,无论是进入还是里离开都能正确执行了。

原因

Vue3 官方文档 Transition一节中,给出了transition组件的触发条件(满足其一):

  • 由 v-if 所触发的切换
  • 由 v-show 所触发的切换
  • 由特殊元素 <component> 切换的动态组件
  • 改变特殊的 key 属性

意思就是说<transition> 组件要正常工作,包裹的内容必须是 “可复用” 的元素或组件

问题根源:router-view 是一个 “动态渲染出口”

其关键在于router-view本身不可复用,它的核心行为如下:

  • 当路由切换时,销毁旧组件实例创建新组件实例
  • router-view 本身不会 “更新”,而是直接替换内部的组件内容。

当直接写 <transition><router-view /></transition> 时,路由更新,transition还没有来得及给旧组件添加离开的效果时,旧的组件实例已经销毁了,这时新的组件实例创建,transition能够正常捕获到该组件实例。而解决方法是通过作用域插槽 "v-slot={Component}" ,把当前路由对应的组件实例暴露出来, 然后使用 component来动态渲染,由于 <component> 本身是一个 “可复用” 的容器,它不会被销毁,只是改变内部渲染的组件。可以让 <transition> 正常监听组件的 “离开” 和 “进入”,从而执行完整的过渡动画。

总结

总之,路由切换过渡动画的核心是让 <transition> 能正常捕获组件的 “离开” 与 “进入” 状态(满足官方给出的触发条件)。避开直接包裹 <router-view> 的误区,同时呢也需要注意样式隔离、动画执行顺序等细节,这样就能实现完整的路由过渡效果。