如何在 Vue Router 中实现路由的过渡动画?

欢迎来到今天的“Vue Router 动画魔术秀”!

大家好,我是你们今天的魔术师(兼讲师),今天我们要一起揭秘 Vue Router 路由切换时的动画效果,让你的页面切换不再生硬,而是像丝绸般顺滑!准备好学习了吗? Let’s go!

一、动画的基础:transition 组件

在 Vue 的世界里,动画的核心武器就是 transition 组件。它能侦测到组件的进入、离开以及更新,并在这些时刻添加 CSS 类名,让我们能通过 CSS 控制动画。

  • transition 组件的基本结构:

    <transition name="fade">
      <router-view />
    </transition>

    这里的 name 属性非常重要,它会影响到自动生成的 CSS 类名。 比如 name="fade", Vue 会自动生成以下类名:

    类名 描述
    fade-enter-from 进入过渡的起始状态。在元素插入之前添加,在元素插入之后移除。你可以理解为元素“即将进入”的状态。
    fade-enter-active 进入过渡的激活状态。在整个进入过渡阶段应用。它会被用来定义过渡的持续时间、延迟和速度曲线。在元素插入之前添加,在过渡完成之后移除。
    fade-enter-to 进入过渡的结束状态。在元素插入之后添加 (在一个 fade-enter-from 移除的同时),在过渡完成之后移除。你可以理解为元素“正在进入”的状态。
    fade-leave-from 离开过渡的起始状态。在离开过渡开始时添加,在离开过渡完成之后移除。你可以理解为元素“即将离开”的状态。
    fade-leave-active 离开过渡的激活状态。在整个离开过渡阶段应用。它会被用来定义过渡的持续时间、延迟和速度曲线。在离开过渡开始时添加,在过渡完成之后移除。
    fade-leave-to 离开过渡的结束状态。离开过渡开始时添加 (在一个 fade-leave-from 移除的同时),在过渡完成之后移除。你可以理解为元素“正在离开”的状态。
  • 简单的淡入淡出动画:

    <template>
      <transition name="fade" mode="out-in">
        <router-view :key="$route.fullPath" />
      </transition>
    </template>
    
    <style scoped>
    .fade-enter-from {
      opacity: 0;
    }
    
    .fade-enter-active {
      transition: opacity 0.5s ease;
    }
    
    .fade-enter-to {
      opacity: 1;
    }
    
    .fade-leave-from {
      opacity: 1;
    }
    
    .fade-leave-active {
      transition: opacity 0.5s ease;
      position: absolute; /*重要!*/
      left: 0;
      top: 0;
    }
    
    .fade-leave-to {
      opacity: 0;
    }
    </style>
    • mode="out-in" 这个属性告诉 transition 组件,先让旧组件离开 (out),再让新组件进入 (in)。这样可以避免两个组件同时存在时的混乱。
    • :key="$route.fullPath" 这个 key 属性至关重要,它告诉 Vue,如果路由改变了,router-view 就应该被视为一个新的组件,从而触发过渡。 如果没有这个 key,Vue 可能会认为只是 router-view 内部的内容发生了变化,而不是一个全新的组件,导致过渡失效。 也可以用 $route.path,但是 $route.fullPath 包含 query 参数和 hash。
    • position: absolute; left: 0; top: 0; 非常重要,必须加在 .fade-leave-active 上。否则在离开过渡时,旧组件会占据新的组件的位置,导致动画显示不正确。

二、让动画更智能:动态过渡

上面的例子虽然简单,但只能实现一种动画。如果想根据不同的路由切换方向,应用不同的动画效果,该怎么办呢? 这就需要用到动态过渡。

  • 思路: 在路由切换时,判断是前进还是后退,然后根据方向设置不同的 transition 组件的 name 属性。

  • 实现:

    1. 定义一个混合 (mixin): 用来记录路由切换的方向。

      // src/mixins/routeTransition.js
      export default {
        data() {
          return {
            transitionName: "slide-left" // 默认向左滑动
          };
        },
        watch: {
          $route(to, from) {
            if (to.meta.index > from.meta.index) {
              this.transitionName = "slide-left"; // 前进,向左滑动
            } else {
              this.transitionName = "slide-right"; // 后退,向右滑动
            }
          }
        }
      };
      • meta.index 需要在路由配置中,为每个路由添加一个 index 属性,表示页面的层级关系。 比如,首页的 index 是 0,二级页面的 index 是 1,以此类推。
    2. 在 App.vue 中使用混合:

      <template>
        <transition :name="transitionName" mode="out-in">
          <router-view :key="$route.fullPath" />
        </transition>
      </template>
      
      <script>
      import routeTransition from "./mixins/routeTransition";
      
      export default {
        mixins: [routeTransition]
      };
      </script>
      
      <style scoped>
      /* 向左滑动 */
      .slide-left-enter-from {
        transform: translateX(100%);
      }
      
      .slide-left-enter-active {
        transition: transform 0.3s ease;
      }
      
      .slide-left-enter-to {
        transform: translateX(0);
      }
      
      .slide-left-leave-from {
        transform: translateX(0);
      }
      
      .slide-left-leave-active {
        transition: transform 0.3s ease;
        position: absolute;
        left: 0;
        top: 0;
      }
      
      .slide-left-leave-to {
        transform: translateX(-100%);
      }
      
      /* 向右滑动 */
      .slide-right-enter-from {
        transform: translateX(-100%);
      }
      
      .slide-right-enter-active {
        transition: transform 0.3s ease;
      }
      
      .slide-right-enter-to {
        transform: translateX(0);
      }
      
      .slide-right-leave-from {
        transform: translateX(0);
      }
      
      .slide-right-leave-active {
        transition: transform 0.3s ease;
        position: absolute;
        left: 0;
        top: 0;
      }
      
      .slide-right-leave-to {
        transform: translateX(100%);
      }
      </style>
    3. 配置路由:

      // src/router/index.js
      import Vue from "vue";
      import VueRouter from "vue-router";
      import Home from "../views/Home.vue";
      import About from "../views/About.vue";
      import Detail from "../views/Detail.vue";
      
      Vue.use(VueRouter);
      
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
          meta: {
            index: 0
          }
        },
        {
          path: "/about",
          name: "About",
          component: About,
          meta: {
            index: 1
          }
        },
        {
          path: "/detail/:id",
          name: "Detail",
          component: Detail,
          meta: {
            index: 2
          }
        }
      ];
      
      const router = new VueRouter({
        mode: "history",
        base: process.env.BASE_URL,
        routes
      });
      
      export default router;
      • meta: { index: ... } 每个路由都必须配置 index 属性,用于判断路由切换的方向。

三、更高级的动画:JavaScript 钩子

transition 组件还提供了 JavaScript 钩子,允许我们在动画的各个阶段执行 JavaScript 代码,实现更复杂的动画效果。

  • 常用的钩子:

    钩子 描述
    beforeEnter 在元素被插入到 DOM 之前调用。
    enter 在元素被插入到 DOM 之后调用。
    afterEnter 在进入过渡完成之后调用。
    enterCancelled 当进入过渡被取消时调用。
    beforeLeave 在离开过渡开始之前调用。
    leave 在离开过渡开始之后调用。
    afterLeave 在离开过渡完成之后调用。
    leaveCancelled 当离开过渡被取消时调用。
  • 使用 JavaScript 钩子的例子:

    <template>
      <transition
        :name="transitionName"
        mode="out-in"
        @before-enter="beforeEnter"
        @enter="enter"
        @after-enter="afterEnter"
        @before-leave="beforeLeave"
        @leave="leave"
        @after-leave="afterLeave"
      >
        <router-view :key="$route.fullPath" />
      </transition>
    </template>
    
    <script>
    import routeTransition from "./mixins/routeTransition";
    
    export default {
      mixins: [routeTransition],
      methods: {
        beforeEnter(el) {
          console.log("beforeEnter", el);
          // 在元素被插入到 DOM 之前执行的代码
        },
        enter(el, done) {
          console.log("enter", el);
          // 在元素被插入到 DOM 之后执行的代码
          // 可以使用 `done` 回调函数来手动控制过渡的完成
          // 例如,使用 JavaScript 动画库来实现更复杂的动画
          // 在动画完成后调用 `done()`
          setTimeout(() => {
            done(); // 必须调用 done()
          }, 500);
        },
        afterEnter(el) {
          console.log("afterEnter", el);
          // 在进入过渡完成之后执行的代码
        },
        beforeLeave(el) {
          console.log("beforeLeave", el);
          // 在离开过渡开始之前执行的代码
        },
        leave(el, done) {
          console.log("leave", el);
          // 在离开过渡开始之后执行的代码
          // 同样可以使用 `done` 回调函数来手动控制过渡的完成
          setTimeout(() => {
            done(); // 必须调用 done()
          }, 500);
        },
        afterLeave(el) {
          console.log("afterLeave", el);
          // 在离开过渡完成之后执行的代码
        }
      }
    };
    </script>
    
    <style scoped>
    /* 这里可以保留简单的 CSS 过渡,或者完全使用 JavaScript 控制动画 */
    </style>
    • done 回调函数:enterleave 钩子中,可以接收一个 done 回调函数。 如果使用了 done,就必须在动画完成后调用它,否则 Vue 会认为过渡没有完成,导致页面卡住。
    • JavaScript 动画库: 可以使用 GSAP、Anime.js 等 JavaScript 动画库,在钩子函数中编写更复杂的动画逻辑。

四、使用 transition 组件的一些注意事项:

  • router-view 必须是 transition 组件的直接子元素: 否则动画可能无法正常工作。
  • key 属性: 必须为 router-view 添加 key 属性,并且 key 的值应该是能够唯一标识路由的值,例如 $route.fullPath
  • mode 属性: 根据需要选择 out-inin-out 模式。
  • CSS 类名: 确保 CSS 类名与 transition 组件的 name 属性一致。
  • 性能: 复杂的动画可能会影响页面性能,要注意优化。 尽量使用 CSS 过渡,而不是 JavaScript 动画,因为 CSS 过渡通常性能更好。

五、总结

今天我们一起学习了 Vue Router 中实现路由过渡动画的几种方法:

  • 使用 transition 组件和 CSS 类名实现简单的动画。
  • 使用动态过渡,根据路由切换方向应用不同的动画效果。
  • 使用 JavaScript 钩子,实现更复杂的动画效果。

掌握这些技巧,你就可以让你的 Vue 应用的路由切换更加生动有趣,给用户带来更好的体验!

最后,给大家留一个小作业: 尝试使用 JavaScript 钩子,结合 GSAP 动画库,实现一个更炫酷的路由过渡动画。 期待你们的精彩作品!

今天的“Vue Router 动画魔术秀”就到这里,感谢大家的观看! 我们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注