LOADING

加载过慢请开启缓存 浏览器默认开启

Hexo + ParticleX 实战修复:代码块横向溢出与 Mermaid 流程图显示炸弹图标

2026/10/20 Blog Hexo

图片

本文记录一次 Hexo 博客的实际故障排查与修复过程。遇到的问题包括:代码块中的超长代码行超出显示范围、Mermaid 流程图无法正常渲染并显示炸弹图标。最终通过调整 CSS、修改 Hexo 语法高亮配置,以及编写自定义 Mermaid 渲染脚本,解决了这两个问题。

本文提供完整、可复制的配置和代码,方便遇到相同问题的用户参考。示例基于本文末尾列出的实际测试环境;不同主题版本或插件组合可能需要调整。

一、故障现象与测试环境

1. 故障一:代码块中的长代码行超出显示范围

在博客文章中插入代码块后,部分超长代码行没有自动换行,直接延伸到代码块右侧,超出了页面的正常显示范围。在手机等窄屏设备上,这种问题会更加明显。

预期效果:

  • 代码在当前代码块宽度内自动折行。
  • 保留原始代码的空格和缩进。
  • 不需要手动修改文章中的代码内容。
  • 尽量不影响现有的语法高亮与复制功能。

2. 故障二:Mermaid 流程图显示炸弹图标

在 Markdown 文章中插入 Mermaid 流程图后,网页没有显示正常的流程图,而是显示类似下面的错误:

Syntax error in text

Parse error on line 1:
flowchart LR U["QQ 私聊"] --> NC["N
-------------^
Expecting 'NEWLINE', got 'NODE_STRING'

看到 Syntax error in text,很容易先怀疑 Mermaid 语法写错了。但本次排查发现,原始流程图语法可以通过独立解析测试;故障发生在页面自动渲染流程中,传给 Mermaid 的源码被合并成了一行,导致解析器无法识别预期的换行。

3. 本次测试环境

项目 环境
博客框架 Hexo 7.3.0
Hexo CLI 4.3.2
Node.js 24.1.0
操作系统 Windows 11 环境下的 Windows 终端
博客主题 Hexo ParticleX
Mermaid 版本 11
Mermaid 插件 hexo-markdown-mermaid

如果你的主题版本、代码块结构或渲染插件与本文不同,可能需要调整 CSS 选择器或脚本。


二、修复代码块超长行溢出

1. 找到 ParticleX 的样式文件

进入 Hexo 博客项目根目录,打开:

themes/particlex/source/css/main.css

建议先备份文件,再进行修改。

2. 在文件末尾添加 CSS

将以下代码添加到 main.css 的最末尾:

/* ParticleX:修复代码块长行溢出 */
.code-content {
    white-space: pre-wrap !important;
    overflow-wrap: anywhere;
    word-break: break-word;
    overflow-x: hidden;
    max-width: 100%;
    box-sizing: border-box;
}

/* 兼容代码高亮和行号区域 */
.code-content pre,
.code-content code,
.code-content .hljs-ln-code {
    white-space: pre-wrap !important;
    overflow-wrap: anywhere;
    word-break: break-word;
}

3. 这段 CSS 的作用

CSS 属性 作用
white-space: pre-wrap 保留原有空白和换行,同时允许长行自动折行
overflow-wrap: anywhere 必要时允许较长的连续字符串换行
word-break: break-word 为长字符串提供额外的换行兼容性
max-width: 100% 限制元素宽度不超过父容器
box-sizing: border-box 让元素的宽度计算包含内边距和边框

这里最重要的是:

white-space: pre-wrap !important;
overflow-wrap: anywhere;

原有代码块的 white-space 设置可能会阻止长行换行,因此需要覆盖该行为。

注意: 本方案针对使用 .code-content 类名的代码块结构。如果你的 ParticleX 版本使用了不同的 DOM 类名,需要根据浏览器开发者工具中的实际元素调整选择器。

这段 CSS 只负责显示效果,不会自动修改 Markdown 源文件。视觉折行也不等于向源代码中插入真正的换行符。

4. 重新生成并刷新博客(或用Git控制,本文以PowerShell为例)

在博客根目录打开 PowerShell,执行:

npx hexo clean
npx hexo generate

如果需要本地预览,可以执行:

npx hexo server

最后在浏览器中按 Ctrl + F5 强制刷新。若博客部署在远程服务器,还需要重新部署生成后的文件。


三、修复 Mermaid 流程图显示炸弹图标

本次最终采用的方案包含三个关键部分:

  1. 调整 Hexo 的语法高亮配置。
  2. 使用 HTML 容器承载 Mermaid 源码。
  3. 添加自定义脚本,从服务器返回的原始 HTML 提取源码,再调用 mermaid.render() 生成 SVG。
flowchart TD A["Markdown 文章"] --> B["Hexo 生成 HTML"] B --> C["读取服务器返回的原始 HTML"] C --> D["提取 Mermaid 源码"] D --> E["mermaid.render()"] E --> F["生成 SVG 流程图"] F --> G["显示在 ParticleX 文章中"]

1. 安装 Mermaid 插件

进入博客根目录,也就是存在 _config.yml 和 package.json 的目录,执行:

npm install hexo-markdown-mermaid --save

这一步用于安装 Mermaid 集成插件,帮助网页加载 Mermaid.js 并提供初始化配置。

2. 修改 Hexo 根目录的 _config.yml

需要修改的是:

_config.yml

而不是:

themes/particlex/_config.yml

本次原有的相关配置为:

syntax_highlighter: highlight.js
highlight:
  line_number: true
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: false
prismjs:
  preprocess: true
  line_number: true
  tab_replace: ''

在本次 Hexo 7.3.0 与 ParticleX 环境中,将内置语法高亮器设置为空,并让 Mermaid 语言绕过普通高亮处理。相关配置修改为:

syntax_highlighter:
highlight:
  line_number: true
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: false
  exclude_languages:
    - mermaid
prismjs:
  preprocess: true
  line_number: true
  tab_replace: ''

mermaid:
  version: 11
  startOnLoad: false
  theme: default

注意以下细节:

  • syntax_highlighter: 必须顶格书写,值留空。
  • exclude_languages 必须放在 highlight: 下面,并比 highlight: 多缩进两个空格。
  • mermaid: 必须顶格书写,与 highlight:、prismjs: 并列。
  • startOnLoad: false 用于关闭默认自动启动的渲染流程,后续由自定义脚本调用渲染 API。
  • 不要在同一个 YAML 文件中重复创建多个 highlight: 或 mermaid: 配置块;如果这些配置已经存在,应当合并。

3. 修改文章中的 Mermaid 写法

原本的流程图可能使用 Markdown 代码围栏:

```mermaid
flowchart LR
    U[QQ 私聊] --> NC[Napcat Docker 容器]
    NC --> AB[AstrBot Docker 容器]
    AB -->|HTTP API:11434/v1| TS[Tailscale 私有网络]
    TS --> OL[Windows 11 · Ollama]
    OL --> GPU[Peach-Framboise GGUF 模型]
    GPU --> VRAM[RTX 3060 8GB]
```

但本次环境中,自动渲染流程存在异常。为了绕过普通代码块处理,最终改用 HTML 容器直接承载 Mermaid 源码。

将文章中的流程图替换为以下完整代码,不要再用三个反引号包住它:

<div class="mermaid">
flowchart LR
    U["QQ 私聊"] --> NC["Napcat Docker 容器"]
    NC --> AB["AstrBot Docker 容器"]
    AB -- "HTTP API: 11434/v1" --> TS["Tailscale 私有网络"]
    TS --> OL["Windows 11 - Ollama"]
    OL --> GPU["Peach-Framboise GGUF 模型"]
    GPU --> VRAM["RTX 3060 8GB"]
</div>

这样生成的 HTML 将包含 .mermaid 容器,而不是普通代码块生成的 <pre><code> 结构。

如果文章中有多个 Mermaid 图表,同样可以使用多个独立的 <div class="mermaid">...</div> 容器。


四、排查过程:如何确认问题出在哪里

如果按照前面的步骤操作后仍然看不到流程图,不建议马上继续修改 CSS 或安装其他插件。本次故障的关键是逐层验证。

1. 确认 Mermaid.js 是否加载成功

打开显示异常的博客页面,按 F12 打开浏览器开发者工具,切换到 Console,执行:

typeof mermaid

预期结果:

'object'

如果返回 undefined,说明当前页面没有可用的 mermaid 对象,需要检查脚本加载和执行情况。

本次排查中,该命令返回了 'object',因此可以排除单纯的脚本未加载问题。

2. 确认当前页面是否存在 Mermaid 容器

执行:

document.querySelectorAll('.mermaid').length

如果页面中只有一个流程图,预期结果为:

1

这只能说明浏览器找到了容器,并不能证明流程图已经正确渲染。

如果仍使用原来的代码块选择器:

document.querySelectorAll('code.mermaid').length

它返回 0 并不奇怪,因为最终方案使用的是 <div class="mermaid">,而不是 <code class="mermaid">。选择器必须与实际 DOM 结构对应。

3. 检查服务器返回的原始 HTML

在 Console 中执行:

const html = await fetch(location.href, {
  cache: 'no-store'
}).then(r => r.text());

const i = html.indexOf('flowchart LR');

console.log(
  i === -1
    ? '没有在网页源 HTML 中找到 flowchart LR'
    : html.slice(Math.max(0, i - 200), i + 700)
);

这段代码会重新读取服务器返回的 HTML,并打印 Mermaid 源码附近的内容。

正常情况下,应当看到类似结构:

<div class="mermaid">
flowchart LR
    U["QQ 私聊"] --> NC["Napcat Docker 容器"]
    NC --> AB["AstrBot Docker 容器"]
</div>

如果这里出现异常的 <pre>、<code> 包装,或者源码被高亮器逐行处理,那么问题还没有解决。

本次排查中,原始 HTML 的结构是正确的,源码也没有残留反引号,因此不能简单归咎于 Markdown 语法。

4. 为什么 SVG 存在,不代表流程图成功?

之前执行过:

document.querySelector('.mermaid svg') !== null

结果为:

true

但页面仍然显示炸弹图标。这说明不能仅凭 SVG 是否存在判断渲染成功:解析失败时,Mermaid 也可能生成包含错误提示的 SVG。

更可靠的判断标准是:

  • 页面显示的是正常流程图,而不是错误图标。
  • Console 没有 Mermaid 解析错误。
  • 节点、连线和标签都正确显示。

5. 关键对比:自动渲染失败,直接渲染成功

自动渲染时报错:

Parse error on line 1:
flowchart LR U["QQ 私聊"] --> NC["N
Expecting 'NEWLINE', got 'NODE_STRING'

错误内容表明,本来应该分行的源码进入解析器后,变成了类似下面的单行内容:

flowchart LR U["QQ 私聊"] --> NC["Napcat Docker 容器"] ...

但通过读取原始 HTML 并直接调用 mermaid.render(),却可以成功生成正常流程图。

这证明了两件事:

  1. 原始源码是可解析的。
  2. 页面原本的自动渲染过程没有正确处理这份源码。

因此,最终方案不再依赖原来的自动渲染路径,而是从服务器读取原始 HTML,提取源码后直接调用 Mermaid 的渲染 API。


五、最终方案:编写自定义 Mermaid 渲染脚本

这是本次解决 Mermaid 问题的核心。

1. 创建脚本目录

在博客根目录下创建:

scripts/

如果该目录已经存在,直接使用即可。然后在其中创建:

scripts/mermaid-fix.js

最终目录结构应类似:

myblog/
├── _config.yml
├── package.json
├── scripts/
│   └── mermaid-fix.js
├── source/
└── themes/
    └── particlex/

Hexo 会自动加载项目根目录 scripts/ 中的脚本。

2. 将以下代码完整复制进去

请直接替换 scripts/mermaid-fix.js 的全部内容,不要把代码追加在旧版脚本后面。

hexo.extend.injector.register(
  'body_end',
  `
<script>
(async function () {
  async function renderMermaidBlocks() {
    if (!window.mermaid) {
      console.error('[Mermaid] Mermaid.js 未加载');
      return;
    }

    try {
      // 从服务器重新获取未经前端脚本修改的 HTML
      const response = await fetch(location.href, {
        cache: 'no-store'
      });

      if (!response.ok) {
        throw new Error(
          '获取原始 HTML 失败:HTTP ' + response.status
        );
      }

      const html = await response.text();

      // 解析原始 HTML,而不是直接读取当前页面 DOM
      const sourceDoc = new DOMParser().parseFromString(
        html,
        'text/html'
      );

      const sourceBlocks = Array.from(
        sourceDoc.querySelectorAll('.mermaid')
      );

      const targetBlocks = Array.from(
        document.querySelectorAll('.mermaid')
      );

      if (sourceBlocks.length !== targetBlocks.length) {
        console.warn(
          '[Mermaid] 源码容器数量与页面容器数量不同',
          sourceBlocks.length,
          targetBlocks.length
        );
      }

      for (
        let i = 0;
        i < Math.min(
          sourceBlocks.length,
          targetBlocks.length
        );
        i++
      ) {
        const source = sourceBlocks[i].textContent
          .replace(/\\r\\n?/g, '\\n')
          .trim();

        const target = targetBlocks[i];

        if (!source) {
          console.warn(
            '[Mermaid] 第 ' + (i + 1) + ' 个图表源码为空'
          );
          continue;
        }

        try {
          console.log(
            '[Mermaid] 源码检查:',
            JSON.stringify(source)
          );

          const result = await window.mermaid.render(
            'mermaid-fixed-' + i + '-' + Date.now(),
            source
          );

          // 用正常生成的 SVG 替换错误图或旧内容
          target.innerHTML = result.svg;

          if (typeof result.bindFunctions === 'function') {
            result.bindFunctions(target);
          }

          console.info(
            '[Mermaid] 图表渲染成功:',
            i + 1
          );
        } catch (error) {
          console.error(
            '[Mermaid] 第 ' + (i + 1) + ' 个图表渲染失败:',
            error
          );
        }
      }
    } catch (error) {
      console.error('[Mermaid] 初始化失败:', error);
    }
  }

  // 等页面及 Mermaid.js 加载完成
  if (document.readyState === 'complete') {
    renderMermaidBlocks();
  } else {
    window.addEventListener(
      'load',
      renderMermaidBlocks,
      { once: true }
    );
  }
})();
</script>
`,
  'default'
);

3. 这段脚本做了什么?

第一阶段:等待 Mermaid.js 加载。

if (!window.mermaid) {
  console.error('[Mermaid] Mermaid.js 未加载');
  return;
}

避免在 Mermaid.js 不存在时直接调用渲染函数。

第二阶段:重新获取服务器返回的 HTML。

const response = await fetch(location.href, {
  cache: 'no-store'
});
const html = await response.text();

这里读取的是当前文章从服务器返回的 HTML,而不是已经经过页面脚本处理的 DOM。

第三阶段:解析原始 HTML。

const sourceDoc = new DOMParser().parseFromString(
  html,
  'text/html'
);

通过 DOMParser 创建独立的文档对象,从原始 HTML 中读取 Mermaid 容器。

第四阶段:提取源码并保留换行。

const source = sourceBlocks[i].textContent
  .replace(/\\r\\n?/g, '\\n')
  .trim();

由于这段代码位于外层 JavaScript 模板字符串中,反斜杠需要经过外层字符串处理。不要在复制时随意删改反斜杠。

第五阶段:直接调用 Mermaid 渲染 API。

const result = await window.mermaid.render(
  'mermaid-fixed-' + i + '-' + Date.now(),
  source
);

这里不再调用原来报错的 mermaid.run(),而是明确将提取出的源码交给 mermaid.render()。

第六阶段:将生成的 SVG 插入页面。

target.innerHTML = result.svg;

如果返回了交互绑定函数,也会调用它:

if (typeof result.bindFunctions === 'function') {
  result.bindFunctions(target);
}

渲染成功后,Console 会出现类似日志:

[Mermaid] 图表渲染成功: 1

数字代表成功渲染的图表序号。


六、重新生成博客并验证

完成配置、文章与脚本修改后,在博客根目录执行:

npx hexo clean
npx hexo generate
npx hexo server

如果已经启动本地服务器,需要先按 Ctrl + C 结束旧进程,再重新执行命令。

部署到远程服务器时,继续使用原有的 Hexo 部署流程即可。

打开文章后,按 Ctrl + F5 强制刷新浏览器。

验证清单

检查项目 预期结果
超长代码行 不再横向溢出
Mermaid 脚本 正常加载
.mermaid 容器 能被浏览器找到
Mermaid 源码 保留原始换行
Console 渲染日志 出现渲染成功信息
流程图 正常显示节点、连线和标签
页面布局 不会因为代码块造成横向溢出

建议至少测试一篇包含长代码块的文章,以及一篇包含 Mermaid 流程图的文章。


七、常见问题与注意事项

1. 修改配置后仍然显示旧页面

可能是浏览器缓存或旧的生成文件尚未清除。建议重新执行:

npx hexo clean
npx hexo generate

然后按 Ctrl + F5 强制刷新浏览器。如果博客部署在 CDN 或反向代理之后,还需要确认访问到的是最新部署的文件。

2. typeof mermaid 返回 undefined

说明当前页面没有可用的 mermaid 对象。应检查:

  • Mermaid.js 脚本请求是否成功。
  • 浏览器 Network 面板中相关资源是否返回成功状态。
  • 是否存在脚本加载错误。
  • 自定义渲染脚本是否在 Mermaid.js 加载之前执行。

3. document.querySelectorAll('code.mermaid').length 返回 0

如果文章已经改用:

<div class="mermaid">
...
</div>

那么 code.mermaid 返回 0 是正常的。此时应该检查:

document.querySelectorAll('.mermaid').length

选择器必须与实际 HTML 结构对应。

4. SVG 存在,但仍然显示炸弹图标

不要仅凭下面的结果判断渲染成功:

document.querySelector('.mermaid svg') !== null

错误提示本身也可能包含 SVG。更可靠的方式是观察实际显示的图形,以及 Console 中是否出现解析错误。

5. 普通代码块与 Mermaid 图表是两个不同的问题

这次排查涉及两种不同的渲染路径:

  • 普通代码块主要由 CSS 和代码高亮结构决定显示效果。
  • Mermaid 图表需要先将流程图源码解析为图形,再由浏览器显示 SVG。

因此,不要试图用一条 CSS 规则同时解决所有问题。

6. 升级主题或插件后,需要重新验证

自定义脚本依赖 .mermaid 选择器。如果将来更新主题、切换 Markdown 渲染器或更换 Mermaid 插件,建议重新检查:

  • 原始 HTML 的结构。
  • Mermaid 源码是否保留换行。
  • 自定义脚本是否仍能找到容器。
  • 是否引入了重复的自动渲染流程。

本方案是在当前 Hexo 7.3.0、ParticleX 和 Mermaid 11 环境下验证成功的,不意味着所有不同版本的主题都必须使用完全相同的脚本。


八、总结

这次修复最重要的经验,不是某一条 CSS 或 JavaScript 代码,而是如何将复杂的前端问题逐层拆解。

第一个问题:代码块长行溢出

通过检查代码块的显示方式,使用 CSS 覆盖 white-space 与换行规则,解决了长行超出页面范围的问题。

第二个问题:Mermaid 显示炸弹图标

通过一系列验证,逐步确认:

  1. Mermaid.js 已经加载。
  2. 原始 HTML 结构正确。
  3. 流程图源码本身能够通过语法检查。
  4. 原有自动渲染过程出现了换行相关的解析错误。
  5. 从服务器原始 HTML 提取源码后,直接调用 mermaid.render() 可以成功生成正常流程图。

最终采用自定义渲染脚本,从原始 HTML 中提取 Mermaid 源码,保留换行并直接生成 SVG,成功解决了问题。

如果你遇到与本文类似的故障,建议按照“检查配置 → 检查原始 HTML → 检查浏览器运行状态 → 验证源码 → 修复渲染流程”的顺序排查,而不是一开始就反复修改语法或安装插件。

这种排查方式不仅适用于 Hexo,也适用于其他依赖 Markdown 渲染、语法高亮和前端 JavaScript 的静态博客系统。

参考资料

本文记录的是实际排查得到的可用方案。由于不同版本的主题、插件、Markdown 渲染器与语法高亮器可能生成不同的 HTML 结构,如果你的环境与本文不同,请优先通过浏览器开发者工具检查生成结果,再调整相应代码。