Hexo博客个性化配置

个性化的内容越来越多,About页面放不下了,于是决定单独写一篇文章记录本博客的配置

主题

NexT.Gemini

  • 版本:v8.18.1

不建议用npm安装,因为那样没法魔改。所以一如既往的直接clone就可以了。

背景图片(ribbon)

本来打算上传背景的,但是图片太大了,最后采用了动态生成的ribbon。

在博客的_config.yml,把canvas_ribbon的enable设置成true。

但是原版的ribbon.js有个小问题,就是如果在手机上点击,会连续刷新两次背景。看了一眼代码,问题出在这儿。

1
2
document.onclick = redraw;
document.ontouchstart = redraw;

因为不喜欢背景频繁变动,我把这两行给删了,同时也就不能用CDN提供的js文件了。

好在hexo允许自行配置vendors。在next的_config.yml里头找到vendors项目,然后加入下面内容(注意改成自己的js文件路径)

1
2
# 这里js文件被我放在 next\source\js\ribbon.min.js
canvas_ribbon: /js/ribbon.min.js

魔改footer

显示hexo和next版本号

页脚显示版本号这个特性在v7的next主题是有的,但是v8给去掉了。我个人比较喜欢这个特性,于是琢磨了半天nunjucks,把显示版本号给魔改回来了。

在footer.njk中把对应内容替换成下面这些~

1
2
3
4
5
6
7
8
9
10
11
12
13
// themes\next\layout\_partials\footer.njk

{%- if theme.footer.powered %}
<div class="powered-info">
<span class="post-meta-item">
{{- __('footer.powered', next_url('https://hexo.io', 'Hexo', {class: 'theme-link'}) + ' v' + hexo_version) }}
</span>
<span class="post-meta-item">
{%- set next_site = 'https://theme-next.js.org' if theme.scheme === 'Gemini' else 'https://theme-next.js.org/' + theme.scheme | lower + '/' %}
{{- __(' 主题 – ' + next_url(next_site, 'NexT.' + theme.scheme, {class: 'theme-link'}) + ' v' + next_version) }}
</span>
</div>
{%- endif %}

然后在locals.js替换下面内容

1
2
3
4
5
6
7
// themes\next\scripts\filters\locals.js

// 替换 const { config } = hexo;
const { version, config } = hexo;

// 添加下面这行
locals.hexo_version = version;

版权信息和字数统计并排

这个也是v7的next主题有的特性,升级到v8又给改没了。。。

这个改起来就很简单了,无非就是把字数统计的span放到版权信息的div里头,如下所示。

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
<div class="copyright">
{%- set copyright_year = date(null, 'YYYY') %}
&copy; {% if theme.footer.since and theme.footer.since != copyright_year %}{{ theme.footer.since }} – {% endif %}
<span itemprop="copyrightYear">{{ copyright_year }}</span>
<span class="with-love">
<i class="{{ theme.footer.icon.name }}"></i>
</span>
<span class="author" itemprop="copyrightHolder">{{ theme.footer.copyright or author }}</span>


{%- if config.symbols_count_time.total_symbols or config.symbols_count_time.total_time %}
{%- if config.symbols_count_time.total_symbols %}
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-chart-line"></i>
</span>
{%- if theme.symbols_count_time.item_text_total %}
<span>{{ __('symbols_count_time.count_total') + __('symbol.colon') }}</span>
{%- endif %}
<span title="{{ __('symbols_count_time.count_total') }}">{{ symbolsCountTotal(site) }}</span>
</span>
{%- endif %}

{%- if config.symbols_count_time.total_time %}
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="fa fa-coffee"></i>
</span>
{%- if theme.symbols_count_time.item_text_total %}
<span>{{ __('symbols_count_time.time_total') }} &asymp;</span>
{%- endif %}
<span title="{{ __('symbols_count_time.time_total') }}">{{ symbolsTimeTotal(site, config.symbols_count_time.awl, config.symbols_count_time.wpm, __('symbols_count_time.time_minutes')) }}</span>
</span>
{%- endif %}
{%- endif %}
</div>

页面载入动画速度

v7的主题也没这个问题,升级到v8之后,首页加载文章的动画速度慢的令人抓狂。在打了不少断点之后,终于搞清楚next主题是如何控制动画速度的了。

hexo的动画是由motion.js进行组装,然后交给anime.js(CDN提供)执行。其中anime.js里头有个参数叫duration,默认值是200,作用是控制动画执行的时间(也就是速度)。这个默认值是在博客的motion.js里头设定的,如下所示。

1
2
3
4
const timeline = window.anime.timeline({
duration: 200,
easing : 'linear'
});

motion.js里头还有个参数叫deltaT,作用是在默认时间基础上进行偏移,如下所示。

1
2
3
4
sequence.forEach(item => {
if (item.deltaT) timeline.add(item, item.deltaT);
else timeline.add(item);
});

那么要改变动画速度,直接修改对应的deltaT就可以了,这里拿文章载入动画速度为例。这里自带的deltaT是-=100,那么duration算出来是100,所以才感觉拖泥带水。最后我改成了-=200,此时duration是0,然后动画就变得干脆利索了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// themes\next\source\js\motion.js

postList: function() {
const sequence = [];
const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition;

function animate(animation, selector) {
if (!animation) return;
document.querySelectorAll(selector).forEach(targets => {
sequence.push({
targets,
complete: () => targets.classList.add('animated', animation),
deltaT : '-=200'
});
});
}

animate(post_block, '.post-block, .pagination, .comments');
animate(coll_header, '.collection-header');
animate(post_header, '.post-header');
animate(post_body, '.post-body');

return sequence;
}

字数统计和阅读时长

hexo-word-counter

  • 版本:v0.1.0
  • 代码块不计入字数(exclude_codeblock: true)

注意:安装完之后一定要执行hexo clean

阅读进度条

next主题的_config.yml里找到Reading progress bar开启

首页隐藏分类(比如公告)

hexo-generator-index2

  • 版本:v0.2.0

禁用更新日期

next主题的_config.yml > post_meta > updated_at > enable: false

全英语链接

全英语链接方便搜索引擎收录,这里简单修改hexo站点的_config.yml即可,下面给出本博客的配置

我的.md文件名都是英语,所以URL > permalink可以设置为:category/:title.html

文章的类别大多都是中文名,可以通过Category & Tag > category_map进行映射

1
2
3
4
5
6
# URL
:category/:title.html

# Category & Tag
category_map:
技术: tech

图片相关

图床(GitHub)

图床我直接使用的 GitHub,新建一个 repo,然后开启 GitHub Page(Settings > Pages)。Branch 选择 main,path 选择 root(根目录)。

然后就可以通过下面方式访问图片了。

1
2
# 假设图片放在 repo 的 img 文件夹里
![](https://{你的博客域名}/{图床repo名称}/img/{你的图片})

图片延迟加载(lazyload)

lazyload就是延迟加载,只有在看到图片的时候才加载,从而加快网页载入速度。

next主题的_config.yml,把lazyload设置成true

但是有些图片我又不希望延迟加载(比如 about 页面的 osu_stats),所以对post.js进行修改

注意的是,这里加入了加载中的占位图,就顺带修复了原有的TOS跳转问题。

1
2
3
4
5
6
7
8
9
10
// 路径:themes\next\scripts\filters\post.js
const placeholderUrl = "https://oscarcx.com/hexo_resource/img/loading_cow.png"; // 替换为你的占位图 URL

if (theme.lazyload) {
// Added the ability to exclude some img with specific attribute
// data.content is the full tag content in plain text
if (data.content.indexOf(theme.lazyload_exclude) == -1) {
data.content = data.content.replace(/(<img[^>]*) src=/img, `$1 src="${placeholderUrl}" data-src=`);
}
}

然后在 next 主题的 _config.yml 增加lazyload_exclude,写上不希望延迟加载的图片 src 即可(src 的任何一部分都行)

1
2
3
4
# Vanilla JavaScript plugin for lazyloading images.
# For more information: https://apoorv.pro/lozad.js/demo/
lazyload: true
lazyload_exclude: osu-sig

图片缩放(mediumzoom)

mediumzoom是一个点击放大图片的插件,挺实用的。

next主题的_config.yml,把mediumzoom设置成true

黑幕(spoiler)

文字模糊处理,点击可见(比如练习题答案)

插件项目地址:unnamed42/hexo-spoiler

安装:npm install hexo-spoiler --save(在博客根目录使用)

使用方式如下所示

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

渲染引擎(renderer)

把 hexo 自带的 hexo-renderer-marked 卸载了,现在使用的是 hexo-renderer-markdown-it-plus

配置如下所示:

1
2
3
markdown_it_plus:
typographer: false
quotes: “”‘’

typographer 这个功能是默认开启的,会在渲染过程中,自动把所有的英语引号全部换成中文引号。具体解释可以看 6.4.0版本之后的两个问题

可以把下面两行复制到文章中进行测试,如果左右渲染出来一样,就是被自动替换了。

'(U+0027) ---> ’(U+2019)

"(U+0022) ---> “(U+201C) + ”(U+201D)

所以只需要 typographer: false 就能禁用这个坑爹的功能

公式显示

直接使用 katex,体积小,而且支持公式的自适应换行,安装方法见这篇官方教程:Math Equations | NexT

hexo 自带的renderer 不支持 katex,所以我安装的是升级版 hexo-renderer-markdown-it-plus

折叠块

本来想自己手搓一个的,发现有人已经写过,效果还不错,就不重复造轮子啦

插件:hexo-content-blocks

需要修改主题的head.njk,然后在 hexo 博客的 config.yml增加配置项目,具体看插件 repo 的 README 文件。

效果如下:

我是折叠块

效果还不错吧~

移动端可折叠目录

移动端显示目录是通过修改css实现,css相关改动全部统一放在 source\_data\styles.styl

目录的折叠需要使用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
51
52
53
54
# 路径:themes\next\layout\_layout.njk
# 在 {{- next_inject('bodyEnd') }} 上方插入这些内容

<script type="text/javascript">
// 显示“展开”和“折叠”
function collapsableTitle(x) {
var nav = document.getElementsByClassName("sidebar-nav-toc")[0];
if (x.matches) { // If media query matches
var content = document.getElementsByClassName("sidebar-panel-container")[0];
if (content.style.display = "none") {
nav.textContent = "文章目录 [展开]";
}
else {
nav.textContent = "文章目录 [折叠]";
}
}
else {
nav.textContent = "文章目录";
}
}

var x = window.matchMedia("(max-width: 991px)"); // media query
collapsableTitle(x);
x.addListener(collapsableTitle);

// 控制目录的折叠
var tos = document.getElementsByClassName("sidebar-nav")[0];
var i;

tos.addEventListener("click", function() {
this.classList.toggle("active");
var content = document.getElementsByClassName("sidebar-panel-container")[0];
var nav = document.getElementsByClassName("sidebar-nav-toc")[0];
if (content.style.display === "block" && window.innerWidth < 991) {
content.style.display = "none";
nav.textContent = "文章目录 [展开]";
} else {
content.style.display = "block";
if (window.innerWidth < 991) {
nav.textContent = "文章目录 [折叠]";
}
}
});

// 因为启用pjax,需要监听complete事件,来实现默认折叠效果
document.addEventListener('pjax:complete', function() {
if (window.innerWidth < 991) {
let content = document.getElementsByClassName("sidebar-panel-container")[0];
content.style.display = "none";
let nav = document.getElementsByClassName("sidebar-nav-toc")[0];
nav.textContent = "文章目录 [展开]";
}
});
</script>

已知问题(不修复):在桌面版阅读文章时,点击“站点概览”,然后拖拽浏览器进入移动端,不显示目录

参考:How To Create a Collapsible - W3Schools

其它改动

微调行距,字体大小等,并且在移动端也显示目录

全部统一放在 source\_data\styles.styl

然后把next主题的_config.yml的这部分取消注释就可以了。写在这个文件里的css会被添加到末尾,所以可以直接覆盖默认样式。

1
2
3
4
5
# Define custom file paths.
# Create your custom files in site directory `source/_data` and uncomment needed files below.
custom_file_path:
(省略若干)
style: source/_data/styles.styl

废弃的魔改

音乐播放器

注:播放器感觉没啥用,已经被我移除

APlayer

  • 版本:v1.10.1
  • 安装方法:把该项目的dist文件夹复制到themes\next\source
  • 页面跳转不打断播放:在主题的_config.yml,把pjax设置成true

要让播放器生效,需要修改_layout.njk

1
2
3
4
5
6
7
# 路径:themes\next\layout\_layout.njk
# 在<div class="headband"></div>下方插入这些内容

<link rel="stylesheet" href="/aplayer/APlayer.min.css">
<div id="aplayer"></div>
<script type="text/javascript" src="/aplayer/APlayer.min.js"></script>
<script type="text/javascript" src="/aplayer/music.js"></script>

使用csv文件来管理曲库,配合Python脚本自动生成music.js

配置

大部分配置项都可以在Aplayer文档找到,这里仅列出我改过的配置。

  • fixed(吸底模式): true
  • preload(预加载): 'metadata'
  • autoplay(自动播放): false
  • order(播放顺序): 'random'

注意:预加载默认是开启的。反正大部分人也不会点播放器,所以只加载metadata就能有效地节约流量。

自动生成music.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
import csv
import json

filename = 'musicDB.csv'
repo_url = 'https://oscarcx.com/hexo_resource/music/'

# 从csv读取name和artist并去掉表头
with open(filename, encoding='UTF-8') as f:
reader = csv.reader(f)
music_list = list(reader)[1:]

# 生成JSON对象
# music_list[i] = [name, artist, url, cover]
# url / cover 为空时,会使用name代替
result = []
for i in range(len(music_list)):
data = {}
data['name'] = music_list[i][0]
data['artist'] = music_list[i][1]
mp3_file_name = music_list[i][2] if music_list[i][2] else music_list[i][0]
data['url'] = repo_url + 'song/' + mp3_file_name + '.mp3'
cover_name = music_list[i][3] if music_list[i][3] else music_list[i][0]
data['cover'] = repo_url + 'cover/' + cover_name + '.jpg'
result.append(data)

# 写入music.js
with open('music.js', 'w', encoding='UTF-8') as f:
f.writelines([
"const ap = new APlayer({" + "\n",
" container: document.getElementById('aplayer')," + "\n",
" fixed: true," + "\n",
" preload: 'metadata'," + "\n",
" autoplay: false," + "\n",
" order: 'random'," + "\n",
" audio: ",
])
json.dump(result, f, ensure_ascii=False, sort_keys=False, indent=4)
f.write("});")

mathjax 长公式

在 next 主题的 _config.yml,可以开启 mathjax(enable: true)

但是,mathjax 长公式在文章中会超出范围,溢出页面。这个问题在移动端上(因为屏幕窄)特别严重。

这篇博客(Hexo+NexT使用MathJax问题)给了我启发。仔细观察可以得知,在 li 元素中的 mathjax 公式并不会溢出,在公式超出区域时会在底部生成滚动条,关键就在于has-jax这个 class。

在 math.styl 中,可以发现下面内容

1
2
3
4
/* themes\next\source\css\_common\components\third-party\math.styl */
.has-jax {
overflow: auto hidden;
}

所以需要在 mathjax.js 中进行如下修改,增加一个 target 是否为 p 元素的判断,这样单行的公式在超出区域时也有滚动条了

1
2
3
4
5
6
7
8
9
// themes\next\source\js\third-party\math\mathjax.js
document.querySelectorAll('mjx-container').forEach(node => {
const target = node.parentNode;
if (target.nodeName.toLowerCase() === 'li') {
target.parentNode.classList.add('has-jax');
} else if (target.nodeName.toLowerCase() === 'p') {
target.classList.add('has-jax');
}
});