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: '彩虹动画',
            },
          },
        ],
      },
    ],
    // 其他配置...
  },
})

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

在这里插入图片描述

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

《shell脚本编程实战手册》中包含了100个拿来就用的《shell脚本案例》[1]。这些实战案例涵盖了各种常见的Shell脚本编程场景,可以帮助读者更好地学习和理解Shell脚本编程。每个案例都有详细的代码示例和解释,让读者能够迅速上手并应用到实际项目中。无论是初学者还是有一定经验的开发者,都能从这些实战案例中获得实用的技巧和经验。除了100个实战案例,手册中还包含了Shell脚本编程的基础知识、流程控制、函数、数组、字符串处理等内容,全面而详实。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [100个超实用的Shell拿来就用脚本示例](https://blog.csdn.net/Dou_Hua_Hua/article/details/121356771)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [shell编程100例](https://blog.csdn.net/admans/article/details/125617465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值