VitePress 系列(1):样式美化

1,118 阅读9分钟

引言

vitepressvue官方推出的 静态站点生成器,专为构建快速、以内容为中心的站点而设计。像 vite官网pinia官网vueUse官网等文档网站都是基于它构建。

VitePress 支持完全的自定义主题,具有标准 Vite + Vue 应用程序的开发体验,它同样可以用来构建个人博客网站。在构建个人博客网站时,vitepress本身的功能可能有点不太够用,下面我将围绕样式美化插件使用自定义组件这三方面去增强vitepress功能,丰富我们的个人博客网站。实践成功后的成果见我的博客

VitePress 系列(1):样式美化

主要是针对 vitePress做的一些样式方面的美化,包括首页彩虹样式动画,以及markdown方面的引用、容器、记号笔、代码块和代码组等功能的美化。

彩虹背景动画

UnoCSS 首页中,它的标题和图片背景有类似彩虹的渐变色动画

具体效果可以看下面这个 GIF 图:

unocss.gif

效果还是挺明显的:左侧 UnoCSS 文字、Getting Started 按钮以及右侧 Logo 都有彩虹渐变背景的动画效果

我们同样可以实现这种效果

theme/style 新建 rainbow.scss 文件,在 rainbow.scss 中 写一个名为 rainbow 关键帧(样式代码详见下方链接)

rainbow 关键帧代码

接着使用这个关键帧

theme/index.ts 中写入代码

/* .vitepress/theme/index.ts */ // [!code focus:3]
// 彩虹背景动画样式
let homePageStyle: HTMLStyleElement | undefined

export default {
  extends: DefaultTheme,

  enhanceApp({app , router }) {
    // [!code focus:8]
    // 彩虹背景动画样式
    if (typeof window !== 'undefined') {
      watch(
        () => router.route.data.relativePath,
        () => updateHomePageStyle(location.pathname === '/'), // 这里需要注意下自己的首页路径是否为 / 
        { immediate: true },
      )
    }

  },
}
// [!code focus:18]
// 彩虹背景动画样式
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
  }
}

这段代码的逻辑是这样的:

  1. 先定义一个动画样式变量 homePageStyle ,类型为 HTMLStyleElement
  2. 创建一个名为 updateHomePageStyle 的函数,函数的作用是根据传入的参数 value 来判断是否需要添加动画样式。如果 value 为真并且 homePageStyle 不存在,则创建一个新的样式元素 homePageStyle ,并设置样式内容为动画样式,使用我们之前创建的关键帧 rainbow 。然后将 homePageStyle 添加到 body 元素中。如果 value 为假,则移除样式元素
  3. 之后使用 watch 监听 路由是否变化,如果路由变化,则执行 updateHomePageStyle 函数,在当前页面是首页的情况下,给函数传 true,否则传 false

::: details 为什么不直接在全局样式中写 animation ?

主要是性能方面的考虑

如果直接在全局 CSS 中添加动画:

:root {
  animation: rainbow 12s linear infinite;
}

那么这个动画会在所有页面都运行,即使你已经离开首页(/)进入其他页面。这会导致:

  • ❌ 动画资源浪费(即使不需要该动画时也持续运行)

  • ❌ 可能影响性能(尤其是复杂的动画或低端设备上)

我们当前代码中通过 Vuewatch 监听路由变化,并调用 updateHomePageStyle(location.pathname === "/") 实现了以下效果:

  • ✔️ 按需加载动画,提升性能

    只在访问首页 / 时才插入带有动画的 <style> 标签,当用户浏览其他页面时,动态移除动画样式,这样可以避免不必要的资源消耗,提升性能

  • ✔️ 精确控制动画生命周期

    通过手动控制动画样式的添加与移除,可以确保动画状态始终与当前页面匹配,避免出现“页面已切换但动画仍在运行”的不一致行为

💡 类似场景举例

这种做法常见于以下场景:

  • 首页背景动画
  • 页面加载特效
  • 某些页面独有的交互动画
  • A/B 测试中的特定样式注入

:::

然后在index.scss中引入rainbow.scss

/* .vitepress/theme/style/index.scss */
@import './rainbow.scss';

rainbow.scss文件只是定义了一个动画关键帧,接下来还需要写一点样式去应用这个关键帧,去覆盖掉首页的背景图,实现线性渐变的彩虹动画效果

theme/style 新建 var.scss 文件,在 var.scss 中写入以下代码:

/**
 * Component: Home
 * -------------------------------------------------------------------------- */
:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: -webkit-linear-gradient(
    120deg,
    var(--vp-c-brand-1) 30%,
    var(--vp-c-brand-next)
  );
  --vp-home-hero-image-background-image: linear-gradient(
    -45deg,
    var(--vp-c-brand-1) 30%,
    var(--vp-c-brand-next)
  );
  --vp-home-hero-image-filter: blur(80px);
}

@media (min-width: 640px) {
  :root {
    --vp-home-hero-image-filter: blur(120px);
  }
}

@media (min-width: 960px) {
  :root {
    --vp-home-hero-image-filter: blur(120px);
  }
}

最后在index.scss中引入这个文件

/* .vitepress/theme/style/index.scss */
@import './var.scss';

以下所有的样式美化都是在.vitepress/theme/style/index.scss中引入的,为了让所写的样式美化生效,在docs\.vitepress\theme\index.ts中需要引入这个index.scss文件

深浅模式切换动画

在官方的文档中,有这么一个 深浅模式切换的动画

有点意思,我们直接抄过来

<!-- .vitepress/theme/MyLayout.vue -->
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import { useData } from 'vitepress'
import { nextTick, provide } from 'vue'

const { isDark } = useData()

const enableTransitions = () =>
  'startViewTransition' in document &&
  window.matchMedia('(prefers-reduced-motion: no-preference)').matches

provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
  if (!enableTransitions()) {
    isDark.value = !isDark.value
    return
  }

  const clipPath = [
    `circle(0px at ${x}px ${y}px)`,
    `circle(${Math.hypot(
      Math.max(x, innerWidth - x),
      Math.max(y, innerHeight - y)
    )}px at ${x}px ${y}px)`
  ]

  await document.startViewTransition(async () => {
    isDark.value = !isDark.value
    await nextTick()
  }).ready

  document.documentElement.animate(
    { clipPath: isDark.value ? clipPath.reverse() : clipPath },
    {
      duration: 300,
      easing: 'ease-in',
      pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
    }
  )
})
</script>

<template>
  <DefaultTheme.Layout>
    <!-- 这里是已有的插槽组件 -->
  </DefaultTheme.Layout>
</template>

<style>
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(root),
.dark::view-transition-new(root) {
  z-index: 1;
}

::view-transition-new(root),
.dark::view-transition-old(root) {
  z-index: 9999;
}

/* 恢复原始开关按钮 */
/* .VPSwitchAppearance {
  width: 22px !important;
} */

.VPSwitchAppearance .check {
  transform: none !important;
}

/* 修正因视图过渡导致的按钮图标偏移 */
.VPSwitchAppearance .check .icon {
  top: -2px;
}
</style>

然后还需要在.vitepress/theme/index.ts中配置下

// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { h } from 'vue' // h函数
// 组件1
import MyLayout from "./components/MyLayout.vue";

export default {
  extends: DefaultTheme,
  Layout() {
    return h(MyLayout, null, {

      // 这里是其他插槽组件

    })
  }
}

看下效果:

modelChange.gif

还不赖

引用颜色更改

Markdown 中,我们常用的引用符号是 >,关于引用的样式我们我们可以稍微改动一下

theme/style 新建 blockquote.css 文件,并且复制下面代码,粘贴到 blockquote.css

/* .vitepress/theme/style/blockquote.css */
.vp-doc blockquote {
  border-radius: 10px;
  padding: 18px 20px 20px 15px;
  position: relative;
  background-color: var(--vp-c-gray-soft);
  border-left: 6px solid var(--vp-c-green-2);
}

然后在 index.scss 中引入生效

/* .vitepress/theme/style/index.scss */
@import "./blockquote.css";

输入:

> 更新时间:2024 年

输出:

更新时间:2024 年

容器颜色

vitePresstipwarningdanger 等容器的样式不太好看,这里我们参考Vuepress/hope 主题的容器颜色去实现一套我们自己的方案

theme/style 新建 custom-block.css 文件,复制下面代码,粘贴到 custom-block.scss

custom-block.scss 代码

更改之前效果:

image.png

更改之后效果:

image.png

更改之后加了左边框、图标,看着好看多了

导航毛玻璃

theme/style 文件夹,然后新建 blur.css 并填入如下代码

/* .vitepress\theme\style\blur.css */
:root {
  /* 首页下滑后导航透明 */
  .VPNavBar:not(.has-sidebar):not(.home.top) {
    background-color: rgba(255, 255, 255, 0);
    backdrop-filter: blur(10px);
  }

  /* 搜索框透明 */
  .DocSearch-Button {
    background-color: rgba(255, 255, 255, 0);
    backdrop-filter: blur(10px);
  }

  /* Feature透明 */
  .VPFeature {
    border: none;
    box-shadow: 0 10px 30px 0 rgb(0 0 0 / 15%);
    background-color: transparent;
  }

  /* 文档页侧边栏顶部透明 */
  .curtain {
    background-color: rgba(255, 255, 255, 0);
    backdrop-filter: blur(10px);
  }

  @media (min-width: 960px) {
    /* 文档页导航中间透明 */
    .VPNavBar:not(.home.top) .content-body {
      background-color: rgba(255, 255, 255, 0);
      backdrop-filter: blur(10px);
    }
  }

  /* 移动端大纲栏透明 */
  .VPLocalNav {
    background-color: rgba(255, 255, 255, 0);
    backdrop-filter: blur(10px);
  }
}

最后引入 index.scss 中 即可看到效果

/* style/index.scss */
@import "./blur.css";

更改之前效果:

blurBefore.gif

更改之后效果:

blurAfter.gif

相比较更改之前导航栏纯白的背景,更改之后的导航栏有一个毛玻璃的效果,体验感会更好

记号笔

在某些整段的文字中,我们可以用记号笔,划出重点。这里的记号笔效果参考了尤大的个人主页

theme/style 新建 marker.css 文件,将下面代码,复制粘贴到 marker.css

/* .vitepress/theme/style/marker.css */

/* 尤雨溪主页记号笔效果 不喜欢可自行调整 */
.marker {
  white-space: nowrap;
  position: relative;
}

.marker:after {
  content: "";
  position: absolute;
  z-index: -1;
  top: 66%;
  left: 0em;
  right: 0em;
  bottom: 0;
  transition: top 200ms cubic-bezier(0, 0.8, 0.13, 1);
  background-color: rgba(79, 192, 141, 0.5);
}

.marker:hover:after {
  top: 0%;
}

然后在 index.scss 中引入生效

/* .vitepress/theme/style/index.scss */
@import "./marker.css";

输入:

<sapn class="marker">这里是尤雨溪的主页样式,鼠标放在我上面看效果</sapn>

输出:

marker.gif

代码块

将代码块改成 Mac 风格,三个小圆点

.vitepress/theme/style 目录新建一个 vp-code.css 文件,复制下面代码,粘贴到 vp-code.css 保存

/* .vitepress/theme/style/vp-code.css */

/* 代码块:增加留空边距 增加阴影 */
.vp-doc div[class*="language-"] {
  box-shadow: 0 10px 30px 0 rgb(0 0 0 / 40%);
  padding-top: 20px;
}

/* 代码块:添加macOS风格的小圆点 */
.vp-doc div[class*="language-"]::before {
  content: "";
  display: block;
  position: absolute;
  top: 12px;
  left: 12px;
  width: 12px;
  height: 12px;
  background-color: #ff5f56;
  border-radius: 50%;
  box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
  z-index: 1;
}

/* 代码块:下移行号 隐藏右侧竖线 */
.vp-doc .line-numbers-wrapper {
  padding-top: 40px;
  border-right: none;
}

/* 代码块:重建行号右侧竖线 */
.vp-doc .line-numbers-wrapper::after {
  content: "";
  position: absolute;
  top: 40px;
  right: 0;
  border-right: 1px solid var(--vp-code-block-divider-color);
  height: calc(100% - 60px);
}

.vp-doc div[class*="language-"].line-numbers-mode {
  margin-bottom: 20px;
}

然后在 index.scss 中引入生效

/* .vitepress/theme/style/index.scss */
@import "./vp-code.css";

更改之前效果:

image.png

更改之后效果:

image.png

更改之后加了边框阴影和顶部左侧的小圆点,更好看了


代码组

在更改代码块的基础上,更改代码组样式

.vitepress/theme/style 目录新建一个 vp-code-group.css 文件,复制下面代码,粘贴到 vp-code-group.css 保存

/* .vitepress/theme/style/vp-code-group.css */

/* 代码组:tab间距 */
.vp-code-group .tabs {
  padding-top: 20px;
}

/* 代码组:添加样式及阴影 */
.vp-code-group {
  color: var(--vp-c-black-soft);
  border-radius: 8px;
  box-shadow: 0 10px 30px 0 rgb(0 0 0 / 40%);
}

/* 代码组:添加macOS风格的小圆点 */
.vp-code-group .tabs::before {
  content: " ";
  position: absolute;
  top: 12px;
  left: 12px;
  height: 12px;
  width: 12px;
  background: #fc625d;
  border-radius: 50%;
  box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
}

/* 代码组:修正倒角、阴影、边距 */
.vp-code-group div[class*="language-"].vp-adaptive-theme.line-numbers-mode {
  border-radius: 8px;
  box-shadow: none;
  padding-top: 0px;
}

/* 代码组:隐藏小圆点 */
.vp-code-group div[class*="language-"].vp-adaptive-theme.line-numbers-mode::before {
  display: none;
}

/* 代码组:修正行号位置 */
.vp-code-group .line-numbers-mode .line-numbers-wrapper {
  padding-top: 20px;
}

/* 代码组:修正行号右侧竖线位置 */
.vp-code-group .line-numbers-mode .line-numbers-wrapper::after {
  top: 24px;
  height: calc(100% - 45px);
}

/* 代码组(无行号):修正倒角、阴影、边距 */
.vp-code-group div[class*="language-"].vp-adaptive-theme {
  border-radius: 8px;
  box-shadow: none;
  padding-top: 0px;
}

/* 代码组(无行号):隐藏小圆点 */
.vp-code-group div[class*="language-"].vp-adaptive-theme::before {
  display: none;
}

然后在 index.scss 中引入生效

/* .vitepress/theme/style/index.scss */
@import "./vp-code-group.css";

相关链接

我的博客
VitePress官网 - 扩展默认主题