Hexo 博客 Fluid 主题实现代码折叠和文字遮盖效果

本文最后更新于:2024年11月4日 早上

Hexo&Fluid 代码折叠功能

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
# or using yarn
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

使用示例

直接使用的话,会发现代码块和 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
.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-spoiler 实现文字遮盖

hexo-sliding-spoiler 是受 hexo-spoiler 插件的启发而成的。hexo-spoiler 同样可以帮助你做到文字遮挡效果:

在线演示:http://htmlpreview.github.io/?https://github.com/unnamed42/hexo-spoiler/blob/master/example/index.html

recording

安装命令

1
npm install hexo-spoiler --save

和上面的插件一样,如果没有起作用可能是配置文件得配置一下

1
2
plugins:
 - hexo-spoiler

语法

1
{% spoiler option:value text... %}

option:value 中的 value 为可配置选项:

Optionname 选项名Type 类型value 值Effect 效果
stylestringblur or box文本将被模糊处理或是被方框覆盖
colorstringAll valid css color
NO spaces allowed for inline option!
仅在 style:box 时起作用;更改框的颜色。默认颜色为黑色。(不允许内联选项中存在空格)
pboolean(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
# ... other configs
# be top-level
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
// 获取唯一 ID
function getUuid() {
  // 生成一个随机的唯一标识符,由当前时间戳和随机数拼接而成
  return Math.random().toString(36).substring(2, 8) + Date.now().toString(36);
}

// 注册 Hexo 的过滤器,在文章渲染后处理代码块
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; // 处理包含行号的 figure
      } else {
        reg = /(<div class="code-wrapper.+?>)(.+?hljs (.*?)".+?)(<\/div>)/gims; // 处理不包含行号的 div
      }
    } else if (lib === "prismjs") {
      reg = /(<div class="code-wrapper.+?>)(.+?data-language="(.*?)".+?)(<\/div>)/gims; // 处理 PrismJS 的代码块
    }

    // match begin inner lang end offset string 分别表示的意思是:匹配到的内容的开始、内容、语言、结束、偏移量、原始字符串
    data.content = data.content.replace(reg, (match, begin, inner, lang, end, offset, string) => {
      const collapseId = `collapse-${getUuid()}`; // 生成唯一的折叠 ID

      // 创建一个容器,用于包裹按钮和语言提示
      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>
      `;
      // collapse表示默认收起,collapse show表示默认展开
      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 代码来控制折叠内容的显示和隐藏。

处理过程是:

  1. 当收起或展开按钮被点击时,Bootstrap 先查找与 data-target 属性对应的元素(这里是被加上了 id="${collapseId}"collapseDiv)。
  2. 根据当前状态,Bootstrap 会往查找到的带 data-target 值(这里是 collapseId)的标签中添加或移除 show 类,从而控制折叠内容的显示或隐藏。
    人话是:Bootstrap 会自动给 const collapseDiv 加上 show 类或移除来控制展开还是收起。collapseDiv 这里是代码块。
  3. 同时给按钮更新 aria-expanded 属性,以反映当前状态(值为 true 即展开反之则收起)。
  4. 同时给按钮增加 collapsed 类来标识收起状态,若无此类则表示展开。

所以,这里有两个值的设定需要注意:

  1. const collapseDivclass="collapse"
    若类名为 class="collapse" 表示默认将代码块收起,collapse show 表示默认展开。
  2. 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
.collapse-header {
  border-radius: 5px 5px 0 0; /* 设置圆角,使背景更柔和 */
  position: relative; /* 使按钮可以定位 */
  z-index: 1;
  background-color: #303030; /* 设置背景颜色为深灰色 */
  padding: 5px 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; // 使用 button 变量,而不是 collapseBtns
    const codeTypeSpan = button.nextElementSibling; // 获取当前按钮后面的 span 标签
    // console.log('当前获取到的按钮类名为:', btnClassName);
    // console.log('当前获取到的 span 标签为:', codeTypeSpan);
    // console.log('当前检测展开状态:', btnClassName.includes('collapsed'));

    if (btnClassName.includes('collapsed')) { // 判断按钮是否在展开状态
      // 展开之后将 span 标签设为隐藏
      codeTypeSpan.style.display = 'none';
    } else {
      // 收起之后将 span 标签设为显示
      codeTypeSpan.style.display = 'inline-block';
    }
  });
});

这样一来,点击按钮展开之后,header 的代码语言提示问题将会被设为隐藏(并非卸载)。

一个可选配置的优化

为什么说是优化呢?因为实际使用下来,我发现每次都要手动使用鼠标点击那么小的一个按钮实在过于折磨,不如直接点击 header 实现折叠或展开。修改就不多说了,如果你能看懂上面的配置,那么下面的配置也能,因为我只做了小小的修改。

codeFloding.js:将 data-toggledata-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
// 获取唯一 ID
function getUuid() {
  return Math.random().toString(36).substring(2, 8) + Date.now().toString(36);
}

// 注册 Hexo 的过滤器,在文章渲染后处理代码块
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; /* 添加上下左右各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
/**
 * 给代码收起按钮和整个 header 添加监听事件
 */
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');

    // 根据按钮状态显示或隐藏 span
    if (button.classList.contains('collapsed')) {
      // console.log('收起代码块');
      codeTypeSpan.style.display = 'inline-block';
    } else {
      // console.log('展开代码块');
      codeTypeSpan.style.display = 'none';
    }
  });

  button.addEventListener('click', (event) => {
    // 阻止事件冒泡,避免触发 header 的点击事件
    event.stopPropagation();
  });
});

以上内容若有侵权,可联系删除


Hexo 博客 Fluid 主题实现代码折叠和文字遮盖效果
https://4rozen.github.io/archives/Hexo/41513.html
作者
4rozeN
发布于
2024年11月1日
许可协议