注册
web

VitePress 彩虹动画

在查阅 VitePress 具体实践时,我被 UnoCSS 文档中的彩虹动画效果深深吸引。在查看其实现原理之后,本文也将探索如何通过自定义组件和样式增强 VitePress 站点,并实现一个炫酷的彩虹动画效果。


自定义主题


VitePress 允许你通过自定义 Layout 来改变页面的结构和样式。自定义 Layout 可以帮助你更好地控制页面的外观和行为,尤其是在复杂的站点中。


项目初始化


在终端中运行以下命令,初始化一个新的 VitePress 项目:


npx vitepress init

然后根据提示,这次选择自定义主题(Default Theme + Customization):


┌  Welcome to VitePress!

◇ Where should VitePress initialize the config?
│ ./docs

◇ Site title:
│ My Awesome Project

◇ Site description:
│ A VitePress Site

◇ Theme:
│ Default Theme + Customization

◇ Use TypeScript for config and theme files?
│ Yes

◇ Add VitePress npm scripts to package.json?
│ Yes

└ Done! Now run npm run docs:dev and start writing.

Tips:
- Make sure to add docs/.vitepress/dist and docs/.vitepress/cache to your .gitignore file.
- Since you've chosen to customize the theme, you should also explicitly install vue as a dev dependency.

注意提示,这里需要额外手动安装 vue 库:


pnpm add vue

自定义入口文件


找到 .vitepress/theme/index.ts 入口文件:


// <https://vitepress.dev/guide/custom-theme>
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'

export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// <https://vitepress.dev/guide/extending-default-theme#layout-slots>
})
},
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme

里面暴露了一个 Layout 组件,这里是通过 h 函数实现的,我们将其抽离成 Layout.vue 组件。


创建自定义 Layout


VitePress 的 Layout 组件是整个网站的骨架,控制了页面的基本结构和布局。通过自定义 Layout,我们可以完全掌控网站的外观和行为。


为什么要自定义 Layout?



  • 增加特定的布局元素
  • 修改默认主题的行为
  • 添加全局组件或功能
  • 实现特殊的视觉效果(如我们的彩虹动画)

我们在 .vitepress/theme 文件夹中创建 Layout.vue 组件,并将之前的内容转换成 vue 代码:


<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
</script>

<template>
<DefaultTheme.Layout />
</template>


接下来,在 .vitepress/theme/index.ts 中注册自定义 Layout:


// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import CustomLayout from './Layout.vue'

export default {
extends: DefaultTheme,
Layout: CustomLayout,
}

这将会覆盖默认的 Layout,应用你自定义的布局结构。


覆盖原本样式


VitePress 提供了 css 变量来动态修改自带的样式,可以看到项目初始化后在 .vitepress/theme 中有一个 style.css。里面提供了案例,告诉如何去修改这些变量。


同时可以通过该链接查看全部的 VitePress 变量:VitePress 默认主题变量


VitePress 允许我们通过多种方式覆盖默认样式。最常用的方法是创建一个 CSS 文件,并在主题配置中导入。


比如想设置 name 的颜色,就可以通过:


:root {
--vp-home-hero-name-color: blue;
}

引入 UnoCSS


UnoCSS 是一个按需生成 CSS 的工具,可以极大简化 CSS 管理,帮助快速生成高效样式。


在项目中安装 UnoCSS 插件:


pnpm add -D unocss

然后,在 vite.config.ts 中配置 UnoCSS 插件:


import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [UnoCSS()],
}

通过 UnoCSS,可以轻松应用样式而无需写冗余 CSS。例如,使用以下类名快速创建按钮样式:


<button class="bg-blue-500 text-white p-4 rounded-lg hover:bg-blue-600">
按钮
</button>

实现彩虹动画


彩虹动画是本文的主角,主要通过动态改变 CSS 变量值来实现色彩的平滑过渡。


定义彩虹动画关键帧


通过 @keyframes,在不同颜色之间平滑过渡,形成彩虹动画效果。创建 rainbow.css 文件:


@keyframes rainbow {
0% {
--vp-c-brand-1: #00a98e;
--vp-c-brand-light: #4ad1b4;
--vp-c-brand-lighter: #78fadc;
--vp-c-brand-dark: #008269;
--vp-c-brand-darker: #005d47;
--vp-c-brand-next: #009ff7;
}
25% {
--vp-c-brand-1: #00a6e2;
--vp-c-brand-light: #56cdff;
--vp-c-brand-lighter: #87f6ff;
--vp-c-brand-dark: #0080b9;
--vp-c-brand-darker: #005c93;
--vp-c-brand-next: #9280ed;
}
50% {
--vp-c-brand-1: #c76dd1;
--vp-c-brand-light: #f194fa;
--vp-c-brand-lighter: #ffbcff;
--vp-c-brand-dark: #9e47a9;
--vp-c-brand-darker: #772082;
--vp-c-brand-next: #eb6552;
}
75% {
--vp-c-brand-1: #e95ca2;
--vp-c-brand-light: #ff84ca;
--vp-c-brand-lighter: #ffadf2;
--vp-c-brand-dark: #be317d;
--vp-c-brand-darker: #940059;
--vp-c-brand-next: #d17a2a;
}
100% {
--vp-c-brand-1: #00a98e;
--vp-c-brand-light: #4ad1b4;
--vp-c-brand-lighter: #78fadc;
--vp-c-brand-dark: #008269;
--vp-c-brand-darker: #005d47;
--vp-c-brand-next: #009ff7;
}
}

:root {
--vp-c-brand-1: #00a8cf;
--vp-c-brand-light: #52cff7;
--vp-c-brand-lighter: #82f8ff;
--vp-c-brand-dark: #0082a7;
--vp-c-brand-darker: #005e81;
--vp-c-brand-next: #638af8;
animation: rainbow 40s linear infinite;
}

html:not(.rainbow) {
--vp-c-brand-1: #00a8cf;
--vp-c-brand-light: #52cff7;
--vp-c-brand-lighter: #82f8ff;
--vp-c-brand-dark: #0082a7;
--vp-c-brand-darker: #005e81;
--vp-c-brand-next: #638af8;
animation: none !important;
}

这段代码定义了彩虹动画的五个关键帧,并将动画应用到根元素上。注意,我们还定义了不带动画的默认状态,这样就可以通过 CSS 类切换动画的启用/禁用。


实现彩虹动画控制组件


接下来,实现名为 RainbowAnimationSwitcher 的组件,其主要逻辑是通过添加或移除 HTML 根元素上的 rainbow 类来控制动画的启用状态,从而实现页面的彩虹渐变效果。


这个组件使用了 @vueuse/core 的两个工具函数:



  • useLocalStorage 用于在浏览器本地存储用户的偏好设置
  • useMediaQuery 用于检测用户系统是否设置了减少动画

<script lang="ts" setup>
import { useLocalStorage, useMediaQuery } from '@vueuse/core'
import { inBrowser } from 'vitepress'
import { computed, watch } from 'vue'
import RainbowSwitcher from './RainbowSwitcher.vue'

defineProps<{ text?: string; screenMenu?: boolean }>()

const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)').value

const animated = useLocalStorage('animate-rainbow', inBrowser ? !reduceMotion : true)

function toggleRainbow() {
animated.value = !animated.value
}

// 在这里对动画做处理
watch(
animated,
anim => {
document.documentElement.classList.remove('rainbow')
if (anim) {
document.documentElement.classList.add('rainbow')
}
},
{ immediate: inBrowser, flush: 'post' },
)

const switchTitle = computed(() => {
return animated.value ? 'Disable rainbow animation' : 'Enable rainbow animation'
})
</script>

<template>
<ClientOnly>
<div class="group" :class="{ mobile: screenMenu }">
<div class="NavScreenRainbowAnimation">
<p class="text">
{{ text ?? 'Rainbow Animation' }}
</p>
<RainbowSwitcher
:title="switchTitle"
class="RainbowAnimationSwitcher"
:aria-checked="animated ? 'true' : 'false'"
@click="toggleRainbow"
>

<span class="i-tabler:rainbow animated" />
<span class="i-tabler:rainbow-off non-animated" />
</RainbowSwitcher>
</div>
</div>
</ClientOnly>
</template>


<style scoped>
.group {
border-top: 1px solid var(--vp-c-divider);
padding-top: 10px;
margin-top: 1rem !important;
}

.NavScreenRainbowAnimation {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 8px;
padding: 12px;
background-color: var(--vp-c-bg-elv);
max-width: 220px;
}

.text {
line-height: 24px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
}

.animated {
opacity: 1;
}

.non-animated {
opacity: 0;
}

.RainbowAnimationSwitcher[aria-checked='false'] .non-animated {
opacity: 1;
}

.RainbowAnimationSwitcher[aria-checked='true'] .animated {
opacity: 1;
}
</style>


其中 RainbowSwitcher 组件是一个简单的开关按钮。以下是其实现:


<template>
<button class="VPSwitch" type="button" role="switch">
<span class="check">
<span v-if="$slots.default" class="icon">
<slot />
</span>
</span>
</button>

</template>

<style scoped>
.VPSwitch {
position: relative;
border-radius: 11px;
display: block;
width: 40px;
height: 22px;
flex-shrink: 0;
border: 1px solid var(--vp-input-border-color);
background-color: var(--vp-input-switch-bg-color);
transition: border-color 0.25s !important;
}

.check {
position: absolute;
top: 1px;
left: 1px;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: var(--vp-c-neutral-inverse);
box-shadow: var(--vp-shadow-1);
transition: transform 0.25s !important;
}

.icon {
position: relative;
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
overflow: hidden;
}
</style>


挂载组件


.vitepress/theme/index.ts 中,在 enhanceApp 中挂载组件:


// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import CustomLayout from './Layout.vue'

export default {
extends: DefaultTheme,
Layout: CustomLayout,
enhanceApp({ app, router }) {
app.component('RainbowAnimationSwitcher', RainbowAnimationSwitcher)
if (typeof window === 'undefined') return

watch(
() => router.route.data.relativePath,
() => updateHomePageStyle(location.pathname === '/'),
{ immediate: true },
)
},
}

// Speed up the rainbow animation on home page
function updateHomePageStyle(value: boolean) {
if (value) {
if (homePageStyle) return

homePageStyle = document.createElement('style')
homePageStyle.innerHTML = `
:root {
animation: rainbow 12s linear infinite;
}`

document.body.appendChild(homePageStyle)
} else {
if (!homePageStyle) return

homePageStyle.remove()
homePageStyle = undefined
}
}

在导航栏中使用彩虹动画开关


.vitepress/config/index.ts 的配置文件中添加彩虹动画开关按钮:


export default defineConfig({
themeConfig: {
nav: [
// 其他导航项...
{
text: `v${version}`,
items: [
{
text: '发布日志',
link: '<https://github.com/yourusername/repo/releases>',
},
{
text: '提交 Issue',
link: '<https://github.com/yourusername/repo/issues>',
},
{
component: 'RainbowAnimationSwitcher',
props: {
text: '彩虹动画',
},
},
],
},
],
// 其他配置...
},
})

这样,彩虹动画开关就成功加载到导航栏的下拉菜单中。


Snipaste_2025-04-22_21-36-40.png


彩虹动画效果


20250422_213520.gif


如果想查看具体效果,可查看 EasyEditor 的文档。其中关于彩虹动画效果的详细实现看,可以查看内部对应的代码:EasyEditor/docs/.vitepress/theme at main · Easy-Editor/EasyEditor


作者:JinSo
来源:juejin.cn/post/7508591120407576586

0 个评论

要回复文章请先登录注册