跳转到内容

插件相关

时光2025/9/50 0 m

让 VitePress 文档“图文并茂”:vitepress-plugin-legend

GitHub 地址github.com/flingyp/vitepress-plugin-legend


✨ 效果预览:文档瞬间“活”起来!

先睹为快,看看这些酷炫功能:

🧠 Markmap:一键生成可交互脑图

将 Markdown 列表自动转换为可折叠的思维导图,支持缩放、拖拽和节点展开/收起:

📊 Mermaid:专业图表轻松绘制

通过简洁的语法快速生成流程图、时序图、甘特图等,并支持节点交互:

text
graph TD
    A[用户登录] -->|输入密码| B{验证成功?}
    B -->|是| C[进入首页]
    B -->|否| D[提示错误]
    C --> E[加载用户数据]

🚀 快速开始:3 分钟实现图文并茂

第一步:安装插件

bash
pnpm add vitepress-plugin-legend -D
bash
npm install vitepress-plugin-legend -D
bash
yarn add vitepress-plugin-legend -D

第二步:配置 VitePress

更新 .vitepress/config.ts
ts
import { defineConfig } from "vitepress";
import { vitepressPluginLegend } from "vitepress-plugin-legend";

export default defineConfig({
  markdown: {
    config(md) {
      vitepressPluginLegend(md, {
        markmap: { showToolbar: true }, // 启用脑图工具栏
        mermaid: true, // 启用 Mermaid 支持
      }); 
    },
  },
});
更新 .vitepress/theme/index.ts
ts
import type { Theme } from "vitepress";
import DefaultTheme from "vitepress/theme";
import { initComponent } from "vitepress-plugin-legend/component"; 
import "vitepress-plugin-legend/dist/index.css"; 

export default {
  extends: DefaultTheme,
  enhanceApp({ app }) {
    initComponent(app); 
  },
} satisfies Theme;

第三步:在 Markdown 中使用图表

绘制脑图(Markmap):
markdown
```markmap
# 前端面试
### HTML
- 语义化标签
- SEO 优化
### CSS
- Flex 布局
- Grid 布局
### JavaScript
- 闭包
- 事件循环
```

markmap 演示效果:

绘制流程图(Mermaid):
markdown
```mermaid
sequenceDiagram
    participant U as 用户
    participant S as 服务器
    U->>S: 请求登录
    S-->>U: 返回 Token
    U->>S: 携带 Token 请求数据
    S-->>U: 返回用户数据
```

mermaid 演示效果:


🎭 高级用法:组件化调用

除了代码块方式,你还可以使用 Vue 组件 引入外部图表文件:

markdown
<!-- 引入外部脑图文件 -->
<PreviewMarkmapPath path="./mindmap.md" showToolbar />

<!-- 引入外部 Mermaid 文件 -->
<PreviewMermaidPath path="./flowchart.mmd" />

图片查看器 Fancybox

说明

参照维知笔记的配置教程,进行了配置,发现因为 fancybox 的版本更新,有些配置项已经失效,我又翻了 fancybox 的官网,对一些配置项进行了调整,目前已经能够使用。

安装 Fancybox

bash
pnpm install --save @fancyapps/ui
bash
npm install --save @fancyapps/ui
bash
yarn install --save @fancyapps/ui

封装组件 ImgViewer.ts

先封装一个组件 ImgViewer.ts,以便我们统一处理文章中的图片:

  1. 鉴于 Fancybox 的要求,不同图片的 data-fancybox 属性值将归于不同图库,所以我们对图片统一设置此属性
  2. 由于个人在文章图片中没有单独设置 alt 属性,所以统一设置此属性为离图片最近的标题文本

代码如下:

docs/.vitepress/theme/components/ImgViewer.ts
ts
import { nextTick } from "vue";
import "@fancyapps/ui/dist/fancybox/fancybox.css";

// 查找图像之前最近的标题
const findNearestHeading = (imgElement) => {
  // 获取 img 元素的父节点
  let currentElement = imgElement;
  // 循环向上查找
  while (currentElement && currentElement !== document.body) {
    // 在当前元素的前一个兄弟节点中查找 h1-h6 标签
    let previousSibling = currentElement.previousElementSibling;
    while (previousSibling) {
      if (previousSibling.tagName.match(/^H[1-6]$/)) {
        return previousSibling.textContent.replace(/\u200B/g, "").trim(); // 返回找到的标题内容
      }
      previousSibling = previousSibling.previousElementSibling;
    }
    // 如果没有找到,继续向上一级父节点查找
    currentElement = currentElement.parentElement;
  }

  return "";
};

export const bindFancybox = () => {
  nextTick(async () => {
    const { Fancybox } = await import("@fancyapps/ui"); // 采用这种导入方式是为了避免构建报错问题
    const imgs = document.querySelectorAll(".vp-doc img");
    imgs.forEach((img) => {
      const image = img as HTMLImageElement;
      if (!image.hasAttribute("data-fancybox")) {
        image.setAttribute("data-fancybox", "gallery");
      }
      // 赋予 alt 属性
      if (!image.hasAttribute("alt") || image.getAttribute("alt") === "") {
        const heading = findNearestHeading(image);
        image.setAttribute("alt", heading);
      }
      // 赋予 data-caption 属性以便显示图片标题
      const altString = image.getAttribute("alt") || "";
      image.setAttribute("data-caption", altString);
    });

    Fancybox.bind('[data-fancybox="gallery"]', {
      Hash: false, // 禁用hash导航
      Carousel: {
        Thumbs: {
          type: "classic", // 经典缩略图,"modern" 现代缩略图
          showOnStart: false, // 开始不显示缩略图列表
        },
        Toolbar: {
          display: {
            left: ["infobar"],
            middle: [
              "zoomIn",
              "zoomOut",
              "toggle1to1",
              "rotateCCW",
              "rotateCW",
              "flipX",
              "flipY",
            ],
            right: ["slideshow", "thumbs", "close"], // 'slideshow' 自动播放
          },
        },
        transition: "slide",
        Zoomable: {
          Panzoom: {
            maxScale: 4, // 最大缩放比例
          },
        },
      },
    });
  });
};

export const destroyFancybox = async () => {
  const { Fancybox } = await import("@fancyapps/ui");
  Fancybox.destroy();
};

2. 在全局设置中启用组件

我们在全局设置中根据不同生命周期和运行时 API,启用不同的组件方法。代码如下:

docs/.vitepress/theme/index.ts
ts
import DefaultTheme from "vitepress/theme";
import "./style/index.css";
import { inBrowser } from "vitepress";
import { bindFancybox, destroyFancybox } from "./components/ImgViewer"; // 图片查看器
import "./style/fancybox.css";

export default {
  extends: DefaultTheme,
  Layout: MyComponent,
  async enhanceApp({ app, router }) {
    if (inBrowser) {
      router.onBeforeRouteChange = () => {
        destroyFancybox(); // 销毁图片查看器
      };
      router.onAfterRouteChanged = () => {
        bindFancybox(); // 绑定图片查看器
      };
    }
  },
  setup() {
    onMounted(() => {
      bindFancybox();
    });
    onUnmounted(() => {
      destroyFancybox();
    });
  },
};

修改默认样式

由于对 Fancybox 的纯黑背景不太感冒,修改为半透明高斯模糊遮罩层,代码如下:

css
.fancybox__container {
  --fancybox-backdrop-bg: rgba(var(--vp-c-bg-rgb), 0.5);
  --fancybox-bg: transparent;
}

/* 遮罩层样式:强制添加模糊和透明度(覆盖默认深黑) */
.fancybox__container .fancybox__backdrop {
  /* 继承容器的背景色变量,叠加模糊 */
  background: var(--fancybox-backdrop-bg);
  opacity: 0.96;
  backdrop-filter: blur(10px); /* 核心模糊效果 */
}

/* 工具栏样式 */
.fancybox__container .f-carousel__toolbar {
  --f-button-bg: none;
  --f-button-hover-bg: rgba(var(--vp-c-bg-reverse-rgb), 0.1);
  --f-button-color: rgba(var(--vp-c-bg-reverse-rgb), 1);
  --f-button-hover-color: rgba(var(--vp-c-bg-reverse-rgb), 1);
  --f-button-svg-disabled-opacity: 0.2;
  background: rgba(var(--vp-c-bg-rgb), 0.2);
  --f-button-color: #374151; /* 深灰 */
}

/* 图片标题样式 */
.fancybox__container .f-caption {
  color: var(--vp-c-text-1); /* 保持原主题色 */
}

/* 图片适配逻辑 */
.fancybox-image {
  object-fit: initial; /* 避免图片被拉伸/压缩 */
}

/* 缩略图选中状态 */
/* 新版缩略图选中逻辑 */
.f-thumbs.is-classic .f-thumbs__slide.is-selected button:after {
  border-color: var(--weiz-primary-color); /* 主题色替换 */
  /* 覆盖默认选中阴影(可选,若需更明显的选中效果) */
  --f-thumb-selected-shadow: inset 0 0 0 2px var(--weiz-primary-color);
}

VitePress Algolia Twikoo EdgeOne Copyright