大家好!今天我们来聊聊 Hexo 博客中一个非常重要但又常常让人头疼的功能——代码高亮。一篇技术文章,如果代码块乱糟糟的,那阅读体验简直是灾难。
在这篇文章里,我们会深入探讨 Hexo 官方支持的两种高亮库 highlight.js 和 prism.js,并分享一下我在为主题添加“代码块折叠”等自定义功能时踩过的坑和解决思路。
Hexo 的两大代码高亮引擎
首先,Hexo 官方文档告诉我们,它主要支持两个主流的代码高亮库:highlight.js 和 prism.js。
1. highlight.js:默认的“老大哥”
highlight.js 是 Hexo 的默认选项,开箱即用。它本身功能很强大,但当 Hexo 为了实现一个常见功能——显示行号时,情况就变得有趣了。
为了加上行号,Hexo 会用 <figure> 和 <table> 标签把原始的代码块包裹起来,形成一个比较复杂的 HTML 结构。
开启行号后的 HTML 结构:
<figure class="highlight yaml">
<table>
<tbody>
<tr>
<td class="gutter">
<pre><span class="line">1</span><br></pre>
</td>
<td class="code">
<pre><span class="line"><span class="attr">hello:</span><span class="string">hexo</span></span><br></pre>
</td>
</tr>
</tbody>
</table>
</figure>
你看,左边是行号(gutter),右边是代码(code),泾渭分明。
这套结构带来的一个“副作用”就是,你从 highlight.js 官网直接下载的那些漂亮主题 CSS 文件,很可能就没法直接用了。因为官方主题的 CSS 选择器是为下面这种纯粹的结构设计的。
highlight.js 默认的 HTML 结构:
<pre><code class="yaml">
<span class="comment"># _config.yml</span>
<span class="attr">hexo:</span> <span class="string">hexo</span>
</code></pre>
怎么办呢?
如果你就是想用官方主题,又不想自己手动改 CSS 适配,唯一的办法就是“返璞归真”。修改你的站点配置文件 _config.yml:
# _config.yml
highlight:
enable: true
line_number: false # 关闭行号
wrap: false # 关闭外部包裹
hljs: true
当你把 line_number 和 wrap 都设为 false 后,Hexo 就会生成上面那种纯粹的 HTML 结构,这样官方的 CSS 就能无缝衔接了。当然,代价就是牺牲了行号显示。
2. prism.js:更现代的挑战者
prism.js 是另一款非常优秀的高亮库。要使用它,首先得在 _config.yml 里“换将”。
# _config.yml
highlight:
enable: false # 先关闭默认的 highlight.js
prismjs:
enable: true
preprocess: true
line_number: true # 开启行号
prism.js 的优点在于,即使开启行号,它也保持了 <pre><code> 这种简洁的 HTML 结构,这对于我们做二次开发或者自定义样式非常友好。
不过,它也有一个大坑,尤其是在行号显示上。我曾因为这个问题耗费了大量时间。
prism.js 行号对不齐问题
引入官方的行号插件和 CSS 后,你可能会发现行号和代码总也对不齐,像这样:
1 第一行代码
2 第二行代码
3 第三行代码
究其原因,prism.js 是通过在代码末尾追加一个特殊的 <span> 标签来统一渲染行号的。如果你在 CSS 中给 pre 或者 code 标签设置了自定义的 line-height(行高)属性,就极有可能导致左侧代码的行高与右侧行号的行高计算不一致,从而引发错位。
解决方案:
最简单粗暴也最有效的方法就是——**不要给你的 pre 标签单独设置 line-height**。让它继承文章正文的行高,通常问题就能迎刃而解。
当两大引擎都关闭时:裸奔模式
如果你在 _config.yml 中把 highlight.enable 和 prismjs.enable 都设置成了 false,那代码块会变成什么样呢?
它会输出一个最原始的 HTML 结构,没有任何用于高亮的 <span> 标签。
裸奔模式的 HTML 结构:
<pre><code class="yaml">
# _config.yml
hexo: hexo
</code></pre>
这种模式下,代码没有颜色区分,就是纯文本。好处是结构最干净,如果你想引入一个完全自定义的第三方高亮方案,可以从这个最原始的状态开始。
实战:为主题添加代码块折叠与语言显示
了解了上面的底层结构后,我们就可以轻松地添加一些自定义功能了。下面我分享一下为主题添加“代码块折叠”和“语言显示”功能的实现思路。
这个思路对 highlight.js 和 prism.js 都适用,只是需要对两种不同的 HTML 结构做一点适配。
1. 代码块折叠/隐藏
核心原理: 利用 CSS 的 display 属性,通过 JavaScript 来回切换 display: block 和 display: none。
实现步骤:
JS 注入 HTML: 用 JavaScript 找到页面上所有的代码块容器(比如
<figure class="highlight">),在它前面动态插入一个工具栏<div>,里面包含一个折叠按钮。// 伪代码,示意逻辑 const highlightBlocks = document.querySelectorAll('.highlight'); highlightBlocks.forEach(block => { const toolsDiv = document.createElement('div'); toolsDiv.className = 'highlight-tools'; toolsDiv.innerHTML = '<button class="toggle-btn">折叠</button>'; block.parentNode.insertBefore(toolsDiv, block); });CSS 控制显示/隐藏: 我们给工具栏
<div>添加一个状态类,比如closed。当这个类存在时,就隐藏它后面的代码块。/* ~ 是 CSS 中的后续同胞组合器,表示“选择后面所有的兄弟元素” */ .highlight-tools.closed ~ .highlight { display: none; }JS 绑定点击事件: 给折叠按钮添加点击事件。每次点击,就切换
highlight-tools那个<div>是否拥有closed类。// 伪代码,示意逻辑 const toggleButtons = document.querySelectorAll('.toggle-btn'); toggleButtons.forEach(btn => { btn.addEventListener('click', () => { const toolsDiv = btn.parentElement; toolsDiv.classList.toggle('closed'); }); });
这样,一个简单的代码折叠功能就完成了。
2. 显示代码语言
核心原理: 代码块的容器元素上通常会有一个 class 来标识代码的语言,比如 class="highlight yaml" 或 class="language-javascript"。我们用 JS 把它读出来就行。
实现步骤:
JS 截取 Class: 在上一步注入工具栏
<div>的同时,我们找到代码块容器。- 对于
highlight.js,语言在<figure>标签的class里(如highlight yaml)。 - 对于
prism.js,语言在<code>标签的class里(如language-js)。
- 对于
提取并显示: 用字符串处理方法提取出语言名称,然后把它也插入到工具栏里。
// 伪代码,示意逻辑 // ... 在注入工具栏的循环中 ... // 假设 block 是 <figure class="highlight yaml"> let lang = ''; const langMatch = Array.from(block.classList).find(cls => cls !== 'highlight'); if (langMatch) { lang = langMatch; } // 如果是 prism.js,则需要查找 code 标签 if (!lang) { const codeTag = block.querySelector('code[class*="language-"]'); if (codeTag) { lang = codeTag.className.replace('language-', ''); } } // 将语言显示出来 const langSpan = document.createElement('span'); langSpan.className = 'lang-name'; langSpan.textContent = lang; toolsDiv.appendChild(langSpan);
通过这种方式,我们就能灵活地在页面上展示当前代码块所使用的编程语言了。
总结
Hexo 的代码高亮功能看似简单,但深入了解后会发现其底层 HTML 结构对我们的主题开发和自定义有很大影响。
- **
highlight.js**:默认选择,开启行号会改变 HTML 结构,可能与官方主题不兼容。 - **
prism.js**:结构更标准,但在处理行号时要特别小心line-height属性,避免样式错位。 - 自定义功能:无论使用哪种库,只要我们理解了它们生成的 HTML 结构,就能通过 JavaScript 和 CSS 轻松地为其添加折叠、复制代码、显示语言等丰富的功能。
希望这篇文章能帮助你更好地驾驭 Hexo 的代码高亮,打造出既美观又实用的技术博客!