Loading
Skip to content

网站设计

发表: 1/2/2026 更新: 5/21/2026 字数: 0 字 时长: 0 分钟

介绍本站使用的自定义样式和组件。

学习是一种持续的过程,而不是一次性的活动。

VitePress 基础

关于 Markdown 的语法

VitePress 的核心特性之一便是强大的 markdown 支持,本站的所有笔记都是基于 markdown 语法编写的。所以学习 markdown 语法非常重要。

学习资源:

VitePress 的主题配置

学习网站基础的主题配置(如设置导航栏、侧边栏、主页等):

VitePress 官方主题配置
iconhttps://vitepress.dev/zh/reference/default-theme-config

VitePress 的代码块语法

VitePress 中的 markdown 关于代码块有很多扩展用法,如行高亮、代码组、导入代码片段、聚焦等等,参考

VitePress 官方
iconhttps://vitepress.dev/zh/guide/markdown#syntax-highlighting-in-code-blocks

信息容器使用示例

以下枚举主要信息容器的使用示例:

md
::: info
这是一条注释
:::

::: warning
因为可能存在风险,所以需要用户立即关注的关键内容。
:::

::: caution
行为可能带来的负面影响。
:::

::: danger
行为带来的严重后果。
:::

::: tip
有助于用户更顺利达成目标的建议性信息。
:::

> [!NOTE]
> 这是一个注意信息(github 风格)

> [!important]
> 这是一个重要信息(github 风格) 

::: details 点我查看详情
这是一个详情容器
:::

::: details: 特殊平滑详情容器
这是使用 `::: details:` Markdown 语法创建的内容,你可以使用简单的 Markdown 语法来创建带有平滑过渡效果的详情容器,展开/收起时会有流畅的动画效果同时支持多行内容和 Markdown 格式。

该样式参考了 [Docusaurus 官方文档](https://docusaurus.io/zh-CN/docs/markdown-features "")的详情容器。
:::

> 这是一个引用测试

`>:` 这是一个特殊引用 <!-- 使用时去掉反引号 -->

效果如下:

INFO

这是一条注释

WARNING

因为可能存在风险,所以需要用户立即关注的关键内容。

CAUTION

行为可能带来的负面影响。

DANGER

行为带来的严重后果。

TIP

有助于用户更顺利达成目标的建议性信息。

NOTE

这是一个注意信息(github 风格)

IMPORTANT

这是一个重要信息(github 风格)

点我查看详情

这是一个详情容器

这是使用 ::: details: Markdown 语法创建的内容,你可以使用简单的 Markdown 语法来创建带有平滑过渡效果的详情容器,展开/收起时会有流畅的动画效果同时支持多行内容和 Markdown 格式。

该样式参考了 Docusaurus 官方文档的详情容器。

这是一个引用测试

这是一个特殊引用

  • VitePress default
  • VitePress ^1.9.0
  • VitePress beta
  • VitePress caution
  • VitePress blue
  • VitePress green
html
* VitePress <Badge type="info" text="default" />
* VitePress <Badge type="tip" text="^1.9.0" />
* VitePress <Badge type="warning" text="beta" />
* VitePress <Badge type="danger" text="caution" />
* VitePress <Badge type="note" text="blue" />
* VitePress <Badge type="green" text="green" />

MarkerText 组件使用示例

记号笔样式

这是一个记号笔效果(原始)

html
<span class='marker'>这是一个记号笔效果(原始)</span>
<!-- 这是一个记号笔效果(原始){.marker} -->

该原始样式取自千浔物语的记号笔样式,另外本站所展示的信息容器也都取自该博客。

千浔物语
iconhttps://docs.fe-qianxun.com/efficiency/software/vitepress#记号笔

但原始样式存在不能换行的问题,在小屏幕上容易超出屏幕,对移动端极不友好,这里将其转换为行内元素,然后使用背景偏移从而解决问题。

记号笔示例:

在使用 html 元素的时候,必须将 markdown 容器首行的:::与带有 html 元素的代码空行使用,否则对应的容器会失效并显现原来的文本。

记号笔标签的使用

虽然此时记号笔的效果已经够用,但缺少对颜色和背景样式的支持,这里封装了一个<MarkerText>组件,用于支持自定义颜色和背景样式。

下面介绍封装后的<MarkerText>组件的使用。

这是默认颜色(绿色)标记文本
html
<MarkerText>这是默认颜色(绿色)标记文本</MarkerText>
<!-- 或<marker-text></marker-text> -->

属性用法

不同自定义颜色

红色标记文本橙色标记文本黄色标记文本绿色标记文本青色标记文本蓝色标记文本紫色标记文本品红色标记文本

INFO

还可以自定义颜色标记文本

html
<div style="margin: 10px 0;">
  <MarkerText color="red">红色标记文本</MarkerText>
  <MarkerText color="orange">橙色标记文本</MarkerText>
  <MarkerText color="yellow">黄色标记文本</MarkerText>  
  <MarkerText color="green">绿色标记文本</MarkerText>
  <MarkerText color="cyan">青色标记文本</MarkerText>
  <MarkerText color="blue">蓝色标记文本</MarkerText>
  <MarkerText color="purple">紫色标记文本</MarkerText>
  <MarkerText color="magenta">品红色标记文本</MarkerText>
</div>

  > [!info]
  > 还可以<marker-text color="rgba(1, 22, 255, 0.3)">自定义颜色标记文本</marker-text>

Full 状态(完全覆盖)

旁边是被完全覆盖的标记文本

下面来对比一下默认状态和full状态的区别:

这是一段多行文本示例,用于展示 MarkerText 组件在多行情况下的表现。当文本内容超过容器宽度时,会自动换行,并且每行都会正确应用标记效果。
这是一段 full 状态的多行文本示例,用于展示完全覆盖模式下的多行表现。所有行都会被完全覆盖,并且没有悬停效果。
html
<div style="margin: 10px 0;">
  <MarkerText color="blue">这是一段多行文本示例,用于展示 MarkerText 组件在多行情况下的表现。当文本内容超过容器宽度时,会自动换行,并且每行都会正确应用标记效果。</MarkerText>
</div>

<div style="margin: 10px 0;">
  <MarkerText color="red" status="full">这是一段 full 状态的多行文本示例,用于展示完全覆盖模式下的多行表现。所有行都会被完全覆盖,并且没有悬停效果。</MarkerText>
</div>

图片的全屏查看

在 VitePress 中,图片默认是不支持全屏查看的,但可以导入插件实现。本站通过导入vitepress-plugin-image-viewer插件,已实现全屏查看图片。

sh
npm install vitepress-plugin-image-viewer
vitepress-plugin-image-viewer 说明文档
vitepress 插件
https://github.com/T-miracle/vitepress-plugin-image-viewer/blob/main/README_zh.md

主页图片展示

图一 主页图片展示(增加放大全屏功能)

自定义链接组件

链接组件介绍

该组件使用了 unocss 引擎样式以及 unplugin-icons 插件加载 iconify 图标库,实现了自定义的链接组件。

html
<!-- 常用格式 -->
<CustomLink
  href=""
  title=""
  desc=""
/>

如引用 github 仓库可使用该组件:

Vue.js 官方仓库
渐进式 JavaScript 框架
https://github.com/vuejs/vue

而该组件取自 ChoDocs 博客中的组件,看了非常喜欢copy过来用用

ChoDocs 博客
iconhttps://chodocs.cn/

为了方便使用该组件,设置了自动识别 markdown 格式的链接,如[](网址)会自动识别为自定义链接组件。

如果想要原格式的链接,可在网址末尾添加一对双引号""或直接使用 html 标签,如琴殿博客

md
[琴殿博客](https://docs.qindlute.cloud/ "")
<!-- <a href="https://docs.qindlute.cloud/" target="_blank">琴殿博客</a> -->

无独有偶,ChoDocs 博客中还有一个B站视频链接,我也一并借来了,传送门(以ChoDocs 作者视频为例)样式如下:

html
<VideoLink bvId="BV1ky4y1Z7rG">从大学学编程到前端工作,我积累的一些好用的网站,整合在了资源导航页面!</VideoLink>

链接组件拓展

本站给该组件拓展了可自定义图标功能,分为外部图像模式和SVG图标模式。

外部图像模式直接在 CustomLink.vue 中为 hrefSource 添加作为判断条件,然后在 template 中添加自定义图标组件。

vue
<!-- .vitepress/theme/components/CustomLink.vue -->
const hrefSource = computed(() => {
  if (/bilibili\.com/.test(href))
    return 'bilibili'
  <!-- ... -->
  else if (/qindlute/.test(href))
    return 'qindlute'
})

<CustomIcon v-if="hrefSource === 'qindlute'" image="/img/qind_ico.svg" class="w-7 h-7" />

效果如下:

琴殿博客
iconhttps://docs.qindlute.cloud/
html
<CustomLink
  href="https://docs.qindlute.cloud/"
  title="琴殿博客"
/>

SVG图标模式需在 CustomIcom.vue<template v-else> 下添加自制的 SVG 定义,然后在 CustomLink.vue 中如外部图像模式一样配置即可。

vue
<!-- .vitepress/theme/components/CustomIcon.vue -->
<!-- 根据名称渲染对应的图标 -->
<template v-if="name === 'web'">
  <g fill="none">
    <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M25 40H7C5.34315 40 4 38.6569 4 37V11C4 9.34315 5.34315 8 7 8H41C42.6569 8 44 9.34315 44 11V24.9412"></path>
    <path fill="#2F88FF" stroke="currentColor" stroke-width="4" d="M4 11C4 9.34315 5.34315 8 7 8H41C42.6569 8 44 9.34315 44 11V20H4V11Z"></path>
    <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M32 35H44"></path>
    <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M38 29V41"></path>
    <circle r="2" fill="#fff" transform="matrix(0 -1 -1 0 10 14)"></circle>
    <circle r="2" fill="#fff" transform="matrix(0 -1 -1 0 16 14)"></circle>
  </g>
</template>

自定义 Button 使用示例

下面展示按钮的使用:(在夜间模式按钮有另外的效果)

html
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; align-items: center;">
<CustomButton type="brand" href="#">主题按钮</CustomButton>
<CustomButton type="alt" href="#">备用按钮</CustomButton>
<CustomButton type="custom" href="#">自定义按钮</CustomButton>
</div>

<!-- <CustomButton type="类型" href="链接" size="尺寸">按钮文本</CustomButton> -->
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 20px; align-items: center;">
<CustomButton type="custom" href="#" size="small">小号自定义按钮</CustomButton>
<CustomButton type="custom" href="#" size="small" :disabled=true>小号禁用自定义按钮</CustomButton>
<CustomButton type="custom" size="large" :disabled=false>大号自定义按钮</CustomButton>
</div>

按钮效果:

小号自定义按钮

白板容器使用示例

为了方便书写笔记或指定内容,本站自定义了一个 markdown 容器whiteboard,用于在笔记中展示代码、公式、图表等内容,容器内模拟了格子纸及 svg 外观。

NOTE

一般情况下,在 VitePress 中使用了 markdown 的标题语法,都会在内嵌的目录中展示,而如果不想在whiteboard中的标题出现在目录中,需要用 html 标签<h>来代替 markdown 的#标题用法。

md
::: whiteboard 我的笔记本(自定义标题)
<h4>这是白板容器内部,支持所有 Markdown 语法:</h4>

<h4>列表语法</h4>

- 无序列表项 1
- 无序列表项 2
  - 子列表项

1. 有序列表项 1
2. 有序列表项 2

<h4>代码块</h4>

```python
print('hello world')
print('hello world')
name = "VitePress"
print(name)
```
:::

我的笔记本(自定义标题)

这是白板容器内部,支持所有 Markdown 语法:

列表语法

  • 无序列表项 1
  • 无序列表项 2
    • 子列表项
  1. 有序列表项 1
  2. 有序列表项 2

代码块

python
print('hello world')
print('hello world')
name = "VitePress"
print(name)

FAQ 容器使用示例

该容器用于展示常见问题的折叠面板,参考:

github 前端项目
https://github.com/solygambas/html-css-javascript-projects/tree/main/012-FAQ%20collapse

以下展示 FAQ 容器的使用示例:

md
::: faq 基本用法示例
这是一个基本的 FAQ 折叠面板,默认是收起状态。
:::

::: faq active 初始展开示例
这个 FAQ 面板在加载时就会展开。
:::

::: faq
没有标题的 FAQ 面板,会显示默认标题。
:::

多个 FAQ 项{.faq-h1}

<div class="faq-container">

::: faq 问题 1
答案 1
:::

::: faq active 问题 2
答案 2
:::

::: faq 问题 3
答案 3
:::

</div>

基本用法示例

这是一个基本的 FAQ 折叠面板,默认是收起状态。

初始展开示例

这个 FAQ 面板在加载时就会展开。

常见问题

没有标题的 FAQ 面板,会显示默认标题。

多个 FAQ 项

问题 1

答案 1

问题 2

答案 2

问题 3

答案 3

数学公式

VitePress 中支持使用 LaTeX 语法来书写数学公式,本站的 whiteboard 容器可以用作展示数学公式。

原来的 LaTeX 语法生成的公式不会自动换行,会造成超出屏幕从而影响阅读体验,尤其是移动端,因此添加了内部横向滚动条。对于生成的公式,增添了鼠标悬停的外观,方便查看。如果想了解 LaTeX 的更多语法,可参考:

LaTeX Demo

3x1+(1+x)2

技巧:在容器内使用#来单独标记 LaTeX 公式是不会在目录中展示的。

展示公式:h(x)=11+ex

使用自定义容器的注意事项

在自定义 markdown 容器的使用中,由于 markdown 语法是:

md
::: 容器类型 <容器标题>
容器内容
:::

注意

在使用自定义容器时:

  • 容器类型和容器标题之间不能有空格,否则会导致容器解析失败。
  • Markdown 语法必须与 HTML 元素空格使用,因为直接紧跟的 Markdown 语法会被当作内联内容处理,导致解析错误

由于:::只会与最近的且没有容器类型的:::匹配由此闭合容器,而不会与最外层的:::匹配。

这会导致两个容器永远也不可能在第二个容器之后的内容还在第一个容器内(内层容器闭合时会直接终止外层容器的解析,外层容器后续内容会被当作普通 markdown 解析,从而跑出容器外)。

简单来说就是在自定义容器中不能嵌套自定义容器,不过在容器内引用信息容器时,可以使用 github 风格,避免使用:::,例如:

md
::: details 点击展开查看更多
``` python
print('hello world')
print('hello world')
name = "VitePress"
print(name)
```
> [!note] 注意
> 这里面是藏起来的文字

这是底部的文字
:::

效果:

点击展开查看更多
python
print('hello world')
print('hello world')
name = "VitePress"
print(name)

注意

这里面是藏起来的文字

这是底部的文字

如果实在需要嵌套自定义容器,也可以使用 HTML 标签来实现 (需熟悉容器结构) 。例如:

md
::: whiteboard 嵌套示例
  你好  
  早上好

  <div class="custom-block note">
    <div class="custom-block-title">注意</div>
    <p>今天下雨</p>
  </div>

  <a href="/pure.html" target="_self">Link to pure.html</a>

  晚上好
:::

效果如下:

嵌套示例

你好
早上好

注意

今天下雨

晚上好

自定义页面记忆滚动位置

默认情况下,vitepress 不会记忆每个页面的滚动位置,当用户在页面间导航(比如点击返回上一页、切换侧边栏链接)时,目标页面会默认滚动到顶部。 本站自定义了插件,默认所有页面都记忆滑轮位置,但允许用户设置 .vitepress/theme/index.ts 中的 scrollMemoryExcludes 来使得某些路径不记忆滚动位置。

ts
// 配置:不使用滚动记忆功能的路径列表
// 支持精确匹配和前缀匹配(以/结尾)
const scrollMemoryExcludes: string[] = [
  // '/exact-path',          // 精确匹配单个页面
  // '/articles/category/',   // 匹配该目录下所有页面
  '/essays/poetry/',
  '/essays/ci/'
];

下面展示两个脚本文件export-localStorage.jsimport-localStorage.js,分别用于下载及导入 localStorage 数据。

js
// .vitepress/scripts/export-localStorage.js
// 获取localStorage的所有内容
const localStorageData = {};
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  localStorageData[key] = localStorage.getItem(key);
}

// 将数据转换为JSON字符串
const localStorageJson = JSON.stringify(localStorageData, null, 2);

// 打印localStorage内容
console.log('LocalStorage内容:');
console.log(localStorageJson);

// 保存到本地文件
function saveToFile(data, filename) {
  // 创建Blob对象
  const blob = new Blob([data], { type: 'application/json' });
  
  // 创建下载链接
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  
  // 触发下载
  document.body.appendChild(a);
  a.click();
  
  // 清理
  setTimeout(() => {
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }, 100);
}

// 保存localStorage数据到本地文件
saveToFile(localStorageJson, 'localStorage_backup.json');
console.log('LocalStorage数据已保存到本地文件 localStorage_backup.json');

// 返回数据
localStorageData
js
// .vitepress/scripts/import-localStorage.js
// 创建文件选择器
function createFileInput() {
  const input = document.createElement('input');
  input.type = 'file';
  input.accept = '.json';
  input.style.display = 'none';
  
  return input;
}

// 读取文件内容
function readFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      try {
        const content = e.target.result;
        const data = JSON.parse(content);
        resolve(data);
      } catch (error) {
        reject(new Error('文件解析失败,请确保文件是有效的JSON格式'));
      }
    };
    reader.onerror = () => {
      reject(new Error('文件读取失败'));
    };
    reader.readAsText(file);
  });
}

// 导入数据到localStorage
function importToLocalStorage(data) {
  const importedKeys = [];
  const failedKeys = [];
  
  // 清空现有的localStorage数据
  const clearExisting = confirm('是否清空现有的localStorage数据?\n\n点击"确定"清空现有数据,点击"取消"保留现有数据并只添加新数据。');
  
  if (clearExisting) {
    localStorage.clear();
    console.log('已清空现有localStorage数据');
  }
  
  // 导入数据
  for (const [key, value] of Object.entries(data)) {
    try {
      localStorage.setItem(key, value);
      importedKeys.push(key);
    } catch (error) {
      failedKeys.push(key);
      console.error(`导入键 "${key}" 失败:`, error);
    }
  }
  
  return { importedKeys, failedKeys };
}

// 显示导入结果
function showImportResult(result) {
  const { importedKeys, failedKeys } = result;
  
  console.log('====================================');
  console.log('LocalStorage 导入结果');
  console.log('====================================');
  console.log(`成功导入: ${importedKeys.length} 个键`);
  console.log(`失败: ${failedKeys.length} 个键`);
  
  if (importedKeys.length > 0) {
    console.log('成功导入的键:');
    importedKeys.forEach(key => console.log(`- ${key}`));
  }
  
  if (failedKeys.length > 0) {
    console.log('失败的键:');
    failedKeys.forEach(key => console.log(`- ${key}`));
  }
  
  console.log('====================================');
  console.log('导入完成!');
  console.log('====================================');
  
  // 显示成功提示
  alert(`导入完成!\n\n成功导入: ${importedKeys.length} 个键\n失败: ${failedKeys.length} 个键\n\n详细信息请查看控制台。`);
}

// 执行导入
async function importLocalStorageData() {
  try {
    console.log('====================================');
    console.log('LocalStorage 导入工具');
    console.log('====================================');
    console.log('请选择要导入的localStorage备份文件(.json格式)');
    
    // 创建文件输入
    const fileInput = createFileInput();
    document.body.appendChild(fileInput);
    
    // 等待用户选择文件
    const file = await new Promise((resolve) => {
      fileInput.onchange = (e) => {
        const selectedFile = e.target.files[0];
        resolve(selectedFile);
      };
      fileInput.click();
    });
    
    if (!file) {
      console.log('未选择文件,导入取消');
      return;
    }
    
    console.log(`正在读取文件: ${file.name}`);
    
    // 读取文件内容
    const data = await readFile(file);
    console.log('文件读取成功,正在导入数据...');
    
    // 导入数据
    const result = importToLocalStorage(data);
    
    // 显示结果
    showImportResult(result);
    
    // 清理
    document.body.removeChild(fileInput);
    
    return result;
  } catch (error) {
    console.error('导入失败:', error);
    alert(`导入失败: ${error.message}`);
  }
}

// 执行导入
try {
  importLocalStorageData();
} catch (error) {
  console.error('导入过程发生错误:', error);
  alert(`导入过程发生错误: ${error.message}`);
}

console.log('LocalStorage 导入工具已启动,请选择要导入的备份文件');

使用步骤:

  1. F12 打开开发者工具
  2. 切换到 Console 标签
  3. 复制粘贴脚本内容并执行

个人安利一个非常好用的网页标注工具 Web Annotator,这是一个UI精良、功能丰富的浏览器拓展,基本满足用户的各种需求,只是无法在移动端浏览器中使用。

英文朗读组件

关于朗读英文组件的使用,示例:

This is a simple test sentence. Click the speaker icon to hear it read aloud.

html
<ReaderText>

  This is a simple test sentence. Click the speaker icon to hear it read aloud.{.marker}
</ReaderText>

点击文本右上角的按钮即可朗读文本,再次点击停止朗读,第三次点击则重新开始朗读,也可切换不同的文本进行朗读。

更多体验可查看 ReaderText 组件测试页面

WARNING

本组件依赖Web Speech API,而移动浏览器(edge)出于性能和隐私考虑,限制了Web Speech API的某些功能,因此移动端只有默认语音来完成朗读功能。

鼠标点击特效

本站自定义了一个鼠标点击特效插件,当用户点击页面时,会在点击位置显示一个小小的动画效果,模拟鼠标点击的反馈。该组件可在 .vitepress/theme/index.ts 中配置挂载来设置是否启用。

ts
Layout: () => {
  return h(DefaultTheme.Layout, null, {
    'layout-top': () => h(ClickHearts)
  })
},

鼠标点击特效

图六 鼠标点击特效

该特效源自致美化的官网:

致美化官网
专注于视觉美化的研究及交流平台
https://zhutix.com/

视频播放

本站使用了 Vidstack 的视频播放组件,功能较为全面。

html
<media-player title="test" src="/video/test.mp4" poster playsinline>
  <media-provider></media-provider>
  <media-video-layout></media-video-layout>
</media-player>
<!-- 或原始video标签: -->
 <video src="/video/test.mp4" controls></video>

下面是 Vidstack 官方的 Demo 演示:

qindlute's notes