方案
hexo 博客默认是没有代码折叠功能的,如果需要实现代码折叠功能,可以安装 hexo-sliding-spoiler 库、借助 Hexo 过滤器功能或者更换代码高亮渲染引擎。
hexo-sliding-spoiler 实现代码折叠
hexo-sliding-spoiler 提供 demo 演示:https://github.com/fletchto99/hexo-sliding-spoiler/blob/master/img/example.gif
安装命令
1 2 3
| npm install hexo-sliding-spoiler --save
yarn add hexo-sliding-spoiler
|
使用示例
1 2 3
| {% spoiler title %} content {% endspoiler %}
|
带空格的需要使用双引号
1 2 3
| {% spoiler "Several spaces in the title" %} content {% endspoiler %}
|
如果没起作用,可能是 hexo 没有检测到插件,可以到_config.yml 中加上
1 2
| plugins: - hexo-sliding-spoiler
|
使用示例
title
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!"); console.log("Hello, world!");
|
直接使用的话,会发现代码块和 spoiler 区域有间距,而且标题也是默认白色,如果想要修改的话,可以找到 node_modules\hexo-sliding-spoiler\assets\spoiler.css 进行修改。我的修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| .spoiler { margin: 0; padding: 0; border: 1px solid #353535; border-radius: 3px; position: relative; clear: both; }
.spoiler .spoiler-title { background: #303030; margin: 0; padding: 5px 15px; color: #d6d6d6; font-weight: bold; font-size: 13px; display: block; cursor: pointer; }
.spoiler .spoiler-title:before { font-weight: bold; }
.spoiler.collapsed .spoiler-title:before { content: "Show "; }
.spoiler.expanded .spoiler-title:before { content: "Hide "; }
.spoiler .spoiler-content { padding: 0; margin-bottom: 0; -moz-transition-duration: 0.3s; -webkit-transition-duration: 0.3s; -o-transition-duration: 0.3s; transition-duration: 0.3s; -moz-transition-timing-function: ease-in-out; -webkit-transition-timing-function: ease-in-out; -o-transition-timing-function: ease-in-out; transition-timing-function: ease-in-out; } .spoiler .spoiler-content figure { margin: 0; } .spoiler.collapsed .spoiler-content { overflow: auto; max-height: 0; }
.spoiler.expanded .spoiler-content { max-height: 3000px; overflow: auto; }
.spoiler .spoiler-content p:first-child { margin-top: 0 !important; }
|
但还是不太完美,自由度不是很够,也可能是我不太会修改吧。
更换代码高亮渲染器实现代码折叠(目前使用的方案)
将渲染器更换为 Hexo Shiki Plugin
实际上我并不知道该插件可以实现代码折叠,而是在配置该插件时发现的代码折叠功能,该功能在插件内实际是 highlight_height_limit 项,代码超出此项设定的高度则自动折叠。
⚠️注意:
该插件在代码类型的处理上存在 bug,详见:issue
解决办法是所有代码类型以后只写小写。
hexo-spoiler 实现文字遮盖
hexo-sliding-spoiler 是受 hexo-spoiler 插件的启发而成的。hexo-spoiler 同样可以帮助你做到文字遮挡效果:
在线演示:http://htmlpreview.github.io/?https://github.com/unnamed42/hexo-spoiler/blob/master/example/index.html

安装命令
1
| npm install hexo-spoiler --save
|
和上面的插件一样,如果没有起作用可能是配置文件得配置一下
1 2
| plugins: - hexo-spoiler
|
语法
1
| {% spoiler option:value text... %}
|
option:value 中的 value 为可配置选项:
| Optionname 选项名 |
Type 类型 |
value 值 |
Effect 效果 |
| style |
string |
blur or box |
文本将被模糊处理或是被方框覆盖 |
| color |
string |
All valid css color NO spaces allowed for inline option! |
仅在 style:box 时起作用;更改框的颜色。默认颜色为黑色。(不允许内联选项中存在空格) |
| p |
boolean(in _config.yml or front-matter)
string(in inline options) |
empty or any string |
遮盖文字将因 <p> 标签换行而非 < span > 标签。如果你想在剧透文本前后加一个新行,可以添加这个。 对于内联选项,分配任何值(除了 “false”),甚至忽略它会打开它;“false” 意味着关闭。默认状态为关闭。 |
这表的配置项都在_config.yml 中进行配置,例如:
1 2 3 4 5
|
spoiler: style: blur p: true
|
对于单独的一篇文章,你可以在文章的 front-matter 中设置,例如:
1 2 3 4 5 6 7
| --- title: blah blah spoiler: style: box color: yellow p: false ---
|
优先级:inline option 内联选项 > front-matter > _config.yml > default
warning
如果你改变了_config.yml,请运行 hexo clean 清除缓存。
Hexo 过滤器实现代码折叠(若不更换高亮渲染则推荐这个)
Kiyan 佬的文章启发了我,于是我自行钻研了一下才有了这一段。这一段内容实际是我后来才加上的,所以放在了这里,文章顺序有点奇怪见谅。
编写 js
由于 fluid 主题已经引入了 Bootstrap,所以我们可以编写一个 js 文件来满足我们的要求。
在 scripts 目录下创建 codeFloding.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| function getUuid() { return Math.random().toString(36).substring(2, 8) + Date.now().toString(36); }
hexo.extend.filter.register( "after_post_render", (data) => { const { line_number, lib } = hexo.theme.config.code.highlight;
let reg; if (lib === "highlightjs") { if (line_number) { reg = /(<figure class="highlight.+?>)(.+?hljs (.*?)".+?)(<\/figure>)/gims; } else { reg = /(<div class="code-wrapper.+?>)(.+?hljs (.*?)".+?)(<\/div>)/gims; } } else if (lib === "prismjs") { reg = /(<div class="code-wrapper.+?>)(.+?data-language="(.*?)".+?)(<\/div>)/gims; }
data.content = data.content.replace(reg, (match, begin, inner, lang, end, offset, string) => { const collapseId = `collapse-${getUuid()}`;
const collapseContainer = ` <div class="collapse-header"> <button class="collapse-btn collapsed" type="button" data-toggle="collapse" data-target="#${collapseId}"> <svg t="1730562455310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9905"> <path d="M857.766234 511.488511L345.623017 0 297.174825 49.348412 759.846873 511.488511 297.174825 974.651588 345.623017 1022.977023z" fill="#ffffff" p-id="9906"> </path> </svg> </button> <span>${lang}</span> <!-- 显示代码语言 --> </div> `; const collapseDiv = `<div class="collapse" id="${collapseId}">${inner}</div>`; return begin + collapseContainer + collapseDiv + end; }); return data; }, 10000 );
|
这份代码的核心在于 <button class="collapse-btn collapsed"> 中的属性:
1
| data-toggle="collapse" data-target="#${collapseId}"
|
为什么这么说呢?
当使用 data-toggle="collapse" 和 data-target 属性时,Bootstrap 的 JavaScript 会自动识别这些属性并处理折叠效果。这意味着我们不需要手动编写任何其他 JavaScript 代码来控制折叠内容的显示和隐藏。
处理过程是:
- 当收起或展开按钮被点击时,Bootstrap 先查找与
data-target 属性对应的元素(这里是被加上了 id="${collapseId}" 的 collapseDiv)。
- 根据当前状态,Bootstrap 会往查找到的带
data-target 值(这里是 collapseId)的标签中添加或移除 show 类,从而控制折叠内容的显示或隐藏。
人话是:Bootstrap 会自动给 const collapseDiv 加上 show 类或移除来控制展开还是收起。collapseDiv 这里是代码块。
- 同时给按钮更新
aria-expanded 属性,以反映当前状态(值为 true 即展开反之则收起)。
- 同时给按钮增加
collapsed 类来标识收起状态,若无此类则表示展开。
所以,这里有两个值的设定需要注意:
const collapseDiv 的 class="collapse":
若类名为 class="collapse" 表示默认将代码块收起,collapse show 表示默认展开。
const collapseDiv 的类名若为不带 show 的类名表示默认收起,则 <button class="collapse-btn collapsed" 中的类名 class="collapse-btn" 需要加上 collapsed。
即:class="collapse-btn collapsed" 与 collapseDiv 收起或展开的状态保持一致。
例如,我的代码默认行为是将代码块收起,所以要设置 const collapseDiv 为:
1
| const collapseDiv = `<div class="collapse" id="${collapseId}">${inner}</div>`;
|
且按钮 button 应设置为:
1
| <button class="collapse-btn collapsed" ...略...
|
另外,你也可以找一个你喜欢的 svg 图用于设置按钮的图标。
相关推荐:阿里巴巴矢量图库
编写 css
在 source\css\ 目录下创建 codeFloding.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| .collapse-header { border-radius: 5px 5px 0 0; position: relative; z-index: 1; background-color: #303030; padding: 5px 5px; }
.collapse-header span { color: white; }
.collapse-header button { border: none; margin-right: 5px; background-color: #303030; cursor: pointer; transition: transform 0.3s ease; }
.collapse-header button:focus { outline: none; }
.collapse-header button svg { width: 10px; height: 10px; fill: white; transition: transform 0.3s ease; }
.collapse-header .collapse-btn.collapsed { transform: rotate(0deg); }
.collapse-header .collapse-btn { transform: rotate(90deg); }
.collapse.show { border-radius: 0 0 5px 5px; background-color: #303030; } .category-collapse.collapse.show { background-color: transparent; }
|
有兴趣可以自行修改 css 样式使其符合你的审美。
warning
请注意在_config.fluid.yml 中进行引入
一个可选的配置
因为我默认开启了代码块的语言类型显示,所以在代码的右上角会自动显示代码语言,如果点击按钮将代码展开,则会在 header 上出现两个代码语言文字,一左一右不是很美观。
于是我创建了 source\js\watch.js
前情提要
watch.js 是以前写的,你也可以找一个已经写过的 js 文件,在底部写,或是单独创建一个 js 文件用于编写。
为了解决上面的重复问题,编写 js 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const collapseBtns = document.querySelectorAll('.collapse-header button'); collapseBtns.forEach(button => { button.addEventListener('click', () => { const btnClassName = button.className; const codeTypeSpan = button.nextElementSibling;
if (btnClassName.includes('collapsed')) { codeTypeSpan.style.display = 'none'; } else { codeTypeSpan.style.display = 'inline-block'; } }); });
|
这样一来,点击按钮展开之后,header 的代码语言提示问题将会被设为隐藏(并非卸载)。
一个可选配置的优化
为什么说是优化呢?因为实际使用下来,我发现每次都要手动使用鼠标点击那么小的一个按钮实在过于折磨,不如直接点击 header 实现折叠或展开。修改就不多说了,如果你能看懂上面的配置,那么下面的配置也能,因为我只做了小小的修改。
codeFloding.js:将 data-toggle 和 data-target 换到 header 上,并在 watch.js 中手动切换 button 的 class 类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function getUuid() { return Math.random().toString(36).substring(2, 8) + Date.now().toString(36); }
hexo.extend.filter.register( "after_post_render", (data) => { const { line_number, lib } = hexo.theme.config.code.highlight;
let reg; if (lib === "highlightjs") { reg = line_number ? /(<figure class="highlight.+?>)(.+?hljs (.*?)".+?)(<\/figure>)/gims : /(<div class="code-wrapper.+?>)(.+?hljs (.*?)".+?)(<\/div>)/gims; } else if (lib === "prismjs") { reg = /(<div class="code-wrapper.+?>)(.+?data-language="(.*?)".+?)(<\/div>)/gims; }
data.content = data.content.replace(reg, (match, begin, inner, lang, end) => { const collapseId = `collapse-${getUuid()}`;
const collapseContainer = ` <div class="collapse-header" data-toggle="collapse" data-target="#${collapseId}"> <button class="collapse-btn collapsed" type="button"> <svg t="1730562455310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9905"> <path d="M857.766234 511.488511L345.623017 0 297.174825 49.348412 759.846873 511.488511 297.174825 974.651588 345.623017 1022.977023z" fill="#ffffff" p-id="9906"></path> </svg> </button> <span>${lang}</span> </div> `; const collapseDiv = `<div class="collapse" id="${collapseId}">${inner}</div>`; return begin + collapseContainer + collapseDiv + end; }); return data; }, 10000 );
|
codeFloding.css:增加了鼠标悬停在 header 时,将鼠标样式改为 pointer 提示用户
1 2 3 4 5 6 7 8 9
| .collapse-header { border-radius: 5px 5px 0 0; position: relative; z-index: 1; background-color: #303030; padding: 5px 5px; cursor: pointer; } ...略...
|
watch.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
const collapseHeaders = document.querySelectorAll('.collapse-header'); collapseHeaders.forEach(header => { const button = header.querySelector('button'); const codeTypeSpan = header.querySelector('span');
header.addEventListener('click', () => { console.log('header clicked'); button.classList.toggle('collapsed');
if (button.classList.contains('collapsed')) { codeTypeSpan.style.display = 'inline-block'; } else { codeTypeSpan.style.display = 'none'; } });
button.addEventListener('click', (event) => { event.stopPropagation(); }); });
|