前言

默认的Butterfly主题在分类和标签页面没有导航栏,用户需要返回总页面才能切换到其他分类或标签。本文介绍如何通过JavaScript动态注入的方式,在具体分类/标签页面添加一个横向导航栏。


实现思路

由于使用npm安装的Butterfly主题无法直接修改pug模板文件,采用以下方案:

  1. JavaScript动态注入 - 检测页面类型,动态创建导航栏
  2. 从侧边栏获取数据 - 利用侧边栏已有的分类/标签列表
  3. CSS样式美化 - 添加导航栏样式和高亮效果

一、创建JavaScript脚本

source/js/catalog-bar.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 分类/标签页导航栏
(function() {
function initCatalogBar() {
var path = decodeURIComponent(window.location.pathname);
var segments = path.split('/').filter(Boolean);

// 只在具体分类/标签页面显示,不在总页面显示
var isCategoryPage = segments[0] === 'categories';
var isTagPage = segments[0] === 'tags';
var isDetailPage = segments.length >= 2;

if (!((isCategoryPage || isTagPage) && isDetailPage)) return;

// 避免重复添加
if (document.getElementById('catalog-bar')) return;

var content = document.getElementById('content-inner');
if (!content) return;

// 创建导航栏容器
var bar = document.createElement('div');
bar.id = 'catalog-bar';
bar.innerHTML = '<i class="fa-fw fas ' + (isCategoryPage ? 'fa-shapes' : 'fa-tags') + '"></i>' +
'<div id="catalog-list"></div>' +
'<a class="catalog-more" href="' + (isCategoryPage ? '/categories/' : '/tags/') + '">更多</a>';

var listContainer = bar.querySelector('#catalog-list');
var items = [];

if (isCategoryPage) {
// 从侧边栏获取分类数据
var categoryLinks = document.querySelectorAll('.card-category-list-link, .card-category-list a');
categoryLinks.forEach(function(a) {
var nameEl = a.querySelector('.card-category-list-name');
var name = nameEl ? nameEl.textContent.trim() : a.textContent.trim();
name = name.replace(/\(\d+\)/, '').trim();
var href = a.getAttribute('href');
if (href && name && href.includes('/categories/') &&
!items.some(function(i) { return i.href === href; })) {
items.push({ name: name, href: href });
}
});
} else {
// 从侧边栏获取标签数据
var tagLinks = document.querySelectorAll('.card-tag-cloud a');
tagLinks.forEach(function(a) {
var name = a.textContent.trim();
var href = a.getAttribute('href');
if (href && name && href.startsWith('/tags/') &&
!items.some(function(i) { return i.href === href; })) {
items.push({ name: name, href: href });
}
});
}

// 渲染导航项
items.forEach(function(item) {
var div = document.createElement('div');
div.className = 'catalog-list-item';
div.setAttribute('data-href', item.href);
div.innerHTML = '<a href="' + item.href + '">' + item.name + '</a>';
listContainer.appendChild(div);
});

// 如果没有获取到数据,不显示导航栏
if (items.length === 0) return;

// 插入到页面(在文章列表标题之前)
var insertPoint = content.querySelector('.article-sort-title');
if (insertPoint) {
insertPoint.parentNode.insertBefore(bar, insertPoint);
}

// 高亮当前页面
var currentPath = path.replace(/page\/[0-9]+\//g, '').replace(/index\.html$/, '');
if (!currentPath.endsWith('/')) currentPath += '/';

var allItems = listContainer.querySelectorAll('.catalog-list-item');
var currentItem = null;

allItems.forEach(function(item) {
var itemHref = item.getAttribute('data-href');
if (!itemHref) return;

// 解码URL编码的路径(关键:匹配中文路径)
itemHref = decodeURIComponent(itemHref);
itemHref = itemHref.replace(/index\.html$/, '');
if (!itemHref.endsWith('/')) itemHref += '/';

if (itemHref === currentPath) {
currentItem = item;
}
});

if (currentItem) {
currentItem.classList.add('selected');
var list = document.getElementById('catalog-list');
if (list) {
setTimeout(function() {
list.scrollLeft = (currentItem.offsetLeft - list.offsetLeft) -
(list.offsetWidth - currentItem.offsetWidth) / 2;
}, 100);
}
}

// 鼠标滚轮横向滚动
var list = document.getElementById('catalog-list');
if (list) {
list.addEventListener('mousewheel', function(e) {
list.scrollLeft -= e.wheelDelta / 2;
e.preventDefault();
}, false);
}
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initCatalogBar);
} else {
initCatalogBar();
}

// pjax 兼容
document.addEventListener('pjax:complete', initCatalogBar);
})();

二、添加CSS样式

source/css/custom.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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/* 分类/标签导航栏样式 */
#catalog-bar {
padding: 0.4rem 0.8rem;
border-radius: 12px;
display: flex;
align-items: center;
border: 1px solid #e0e0e0;
margin-bottom: 1rem;
background: #fff;
transition: border-color 0.3s ease;
}

#catalog-bar:hover {
border-color: #667eea;
}

#catalog-bar i {
color: #667eea;
margin-right: 0.5rem;
}

#catalog-list {
margin: 0 0.5rem;
display: flex;
white-space: nowrap;
overflow-x: auto;
flex: 1;
scrollbar-width: none;
-ms-overflow-style: none;
}

#catalog-list::-webkit-scrollbar {
display: none;
}

.catalog-list-item a {
display: inline-block;
margin: 0 0.2em;
padding: 0.3em 0.6em;
font-weight: 500;
border-radius: 8px;
color: #555;
font-size: 0.9em;
transition: all 0.3s ease;
text-decoration: none;
}

.catalog-list-item:hover a {
background: #667eea;
color: #fff;
}

.catalog-list-item.selected a {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}

a.catalog-more {
min-width: fit-content;
font-weight: bold;
color: #667eea;
font-size: 0.9em;
text-decoration: none;
margin-left: auto;
}

/* 暗色模式适配 */
[data-theme="dark"] #catalog-bar {
background: #2d2d2d;
border-color: #444;
}

[data-theme="dark"] .catalog-list-item a {
color: #ccc;
}

[data-theme="dark"] .catalog-list-item:hover a,
[data-theme="dark"] .catalog-list-item.selected a {
background: #667eea;
color: #fff;
}

三、注入文件

_config.butterfly.ymlinject 配置中添加:

1
2
3
inject:
bottom:
- <script src="/js/catalog-bar.js"></script>

四、实现效果

页面 导航栏
/categories/ 不显示
/categories/博客搭建/ 显示所有分类,当前分类高亮
/tags/ 不显示
/tags/Hexo/ 显示所有标签,当前标签高亮

功能特点

  • 当前高亮 - 自动高亮当前访问的分类/标签
  • 横向滚动 - 分类/标签过多时支持横向滚动
  • 滚轮支持 - 鼠标滚轮可横向滚动导航栏
  • 更多按钮 - 点击跳转到全部分类/标签页面
  • 暗色模式 - 自动适配深色主题
  • pjax兼容 - 支持pjax无刷新加载

五、踩坑记录

1. 中文路径编码问题

侧边栏生成的href是URL编码的(如 %E5%8D%9A%E5%AE%A2),而 window.location.pathname 是解码后的中文。匹配时需要先 decodeURIComponent() 解码:

1
itemHref = decodeURIComponent(itemHref);

2. 选择器问题

Butterfly主题的侧边栏分类使用 .card-category-list-link 类名,名称在 .card-category-list-name 子元素中:

1
2
var nameEl = a.querySelector('.card-category-list-name');
var name = nameEl ? nameEl.textContent.trim() : a.textContent.trim();

总结

通过JavaScript动态注入的方式,在不修改主题模板文件的前提下,实现了分类和标签页面的导航栏功能。核心要点是正确处理URL编码的中文路径匹配,以及从侧边栏已有的DOM元素获取数据。


参考资料: