VitePress 为路由切换增加动画效果

823 阅读4分钟

简单介绍

在 vitepress 中,文档的切换非常迅速,以至于我们如果注意力不集中很容易没有发现文档切换了,聪明的我们还以为点击链接没有效果或者卡住了

为了解决这个问题,我们需要在路由切换时增加一些贼拉漂亮的动画效果,让文档切换更流畅,更自然


来看看官方文档怎么让路由切换有动画效果吧:

为路由切换增加动画效果-1.webp

现在它真的要到来了!


其实要实现这个效果很简单,不知道官网为啥鸽了
我是基于官网介绍的 布局插槽 实现的

实现的关键点

  1. 使用布局插槽来让显示文档的区域插入一个遮罩层
  2. 给这个遮罩层添加一个可以由 JS 控制的动画效果
  3. 监听路由切换,在路由切换时,控制遮罩层的动画效果

效果展示

test.gif

先展示最终代码,之后再详细讲解

如果你不想看详细解释并对自己的实力有信心,可以不用看下面的详细解释,直接从代码中获取关键信息并在自己的项目中实现路由切换动画。很简单的

.vitepress/theme/MyLayout.vue

<script setup>
import { useRouter } from "vitepress";
import DefaultTheme from "vitepress/theme";
import { watch, ref } from "vue";

const { Layout } = DefaultTheme;
const { route } = useRouter();
const isTransitioning = ref(false);

watch(
  () => route.path,
  () => {
    isTransitioning.value = true;
    // 动画结束后重置状态
    setTimeout(() => {
      isTransitioning.value = false;
    }, 500); // 500ms 要和 CSS 动画时间匹配
  }
);
</script>

<template>
  <Layout>
    <template #doc-top>
      <div class="shade" :class="{ 'shade-active': isTransitioning }">
        &nbsp;
      </div>
    </template>
  </Layout>
</template>

<style>
.shade {
  position: fixed;
  width: 100%;
  height: 100vh;
  background-color: rgb(255, 255, 255);
  z-index: 100;
  pointer-events: none;
  opacity: 0;
  transition: transform 0.5s ease-in-out;
}

.shade-active {
  opacity: 0;
  animation: shadeAnimation 0.5s ease-in-out;
}

@keyframes shadeAnimation {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  50% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateY(100vh);
  }
}
</style>

.vitepress/theme/index.ts

import DefaultTheme from "vitepress/theme";

import { Theme } from "vitepress";

import MyLayout from "./MyLayout.vue";

const theme: Theme = {
  extends: DefaultTheme,
  Layout: MyLayout,
};

export default theme;

给显示文档的区域插入一个遮罩层

主题入口文件

首先要明白如何扩展默认主题

vitepress 中可以通过创建一个 .vitepress/theme/index.js.vitepress/theme/index.ts 文件 (即“主题入口文件”) 来启用自定义主题

在主题入口文件中,可以直接创建一个新的主题来覆盖掉默认主题,但是最好别这样做(vitepress 的默认主题还是很靠谱的)

如果你确实觉得默认主题就是一坨答辩,可以去看看官方文档中的 配置和 API 参考

更多时候,我们希望扩展默认主题,比如添加一个布局插槽,或者修改默认主题的样式

上面的 .vitepress/theme/index.ts 文件是一个没有任何多余东西的扩展主题,它使用注入插槽的包装组件(MyLayout)覆盖原始的 Layout。如果你的项目中没有其它东西修改过 index.ts(vitepress 的新手),那么可以直接复制


MyLayout.vue 布局文件

现在关键来到了 .vitepress/theme/MyLayout.vue 文件

它其实就是一个 Vue 单文件组件,不同的是如果你想要扩展默认主题,那么应该提供 vitepress 暴露的 Layout 组件

<template>
  <Layout>
    <template #doc-top>
      <div class="shade">
        &nbsp;
      </div>
    </template>
  </Layout>
</template>

其中的 #doc-top 是一个插槽的位置,关于插槽的更多内容请查看官方文档 布局插槽

丰富遮罩层的样式并添加可以由 JS 控制的动画效果

如果你熟悉 js、css 和 vue,那么这一步你会很熟悉

<script setup>
import DefaultTheme from "vitepress/theme";
import { ref } from "vue";

const { Layout } = DefaultTheme;
const isTransitioning = ref(false);

</script>

<template>
  <Layout>
    <template #doc-top>
      <div class="shade" :class="{ 'shade-active': isTransitioning }">
        &nbsp;
      </div>
    </template>
  </Layout>
</template>

<style>
.shade {
  position: fixed;
  width: 100%;
  height: 100vh;
  background-color: rgb(255, 255, 255);
  z-index: 100;
  pointer-events: none;
  opacity: 0;
  transition: transform 0.5s ease-in-out;
}

.shade-active {
  opacity: 0;
  animation: shadeAnimation 0.5s ease-in-out;
}

@keyframes shadeAnimation {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  50% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateY(100vh);
  }
}
</style>

代码看起来增加了很多,其实主要是 CSS 动画,如果你非常熟悉 CSS,那么可以自定义你的动画效果。你甚至可以使用 gsap 等动画库(需要安装依赖)。如果你是新手,直接复制吧,还说啥呢

别忘了关键的 isTransitioning 响应式变量,它将是下一步的关键

监听路由切换,控制动画效果

距离完成还有最后一步,如果你熟悉 VueRouter, 那么这一步你会很熟悉

实际上 VitePress 是基于 VueRouter 实现路由跳转的,但是它对 VueRouter 做了封装

要控制路由与原版 VueRouter 一样,关键是获取路由实例的方法变成了 vitepress 提供的 useRouter() 函数

现在在 MyLayout.vue 中获取到路由实例,监听路由的变化,然后在路由变化时控制遮罩层的动画效果

<script setup>
import { useRouter } from "vitepress";
import { watch, ref } from "vue";

const { Layout } = DefaultTheme;
const { route } = useRouter();
const isTransitioning = ref(false);

watch(
  () => route.path,
  () => {
    isTransitioning.value = true;
    // 动画结束后重置状态
    setTimeout(() => {
      isTransitioning.value = false;
    }, 500); // 500ms 要和 CSS 动画时间匹配
  }
);
</script>

注意:如果不监听路由变化,那么动画效果将只会生效一次,具体原因是因为 Layout 组件只会被渲染一次

完成

如果大佬们有更好的方法,可以在评论区留言
萌新们别忘了看看评论区,大佬们总是会在评论区出现