Butterfly 标签云文章数与样式定制记录
这篇文章记录本站最近对 Butterfly 标签云做的一点小改造:通过自定义脚本重写主题的 cloudTags helper,让标签云直接显示每个标签下的文章数量;再通过自定义 CSS 把标签云从默认的活泼展示压成更克制的浅蓝色 pill 风格。
这事不算大功能,更像一次顺手把站点打磨舒服的小维护。不过它刚好碰到 Hexo helper、Butterfly 模板、自定义 CSS、选择器权重和可访问性标记几个点,还是值得记一下。以后再改主题,至少不用重新翻一遍。
为什么要改标签云
Butterfly 默认的标签云会根据标签文章数量给标签设置不同字号。这个设计很直观:文章越多的标签越大,视觉权重也越高。
但放到本站现在的规模里,它有点太“云”了。文章数量不多,标签也不多,单靠字号表达权重时,实际阅读体验并没有变得更清楚。
读者能看出 Cpp 比 Plugin 大,但到底多多少,还得猜。侧栏空间又窄,标签字号一跳,整个区域就会比旁边的分类和归档更抢眼。这个抢眼没有太多收益。
我想要的效果更朴素一点:
- 每个标签后面直接显示文章数量。
/tags/主标签页保持统一字号,减少视觉噪声。- 侧栏标签云仍保留一定字号差异,但用更轻的方式表现权重。
- 不直接修改
node_modules/hexo-theme-butterfly/里的主题源码。 - 样式覆盖只放在
source/css/custom.css,继续通过 Butterfly 的inject.head注入。
也就是把标签云改得更像一个能扫读的索引,而不是一个负责制造气氛的装饰块。
用脚本重写 cloudTags
Hexo 会自动加载项目根目录 scripts/ 下的脚本。本站这次加的逻辑放在 scripts/tag-count-display.js,做法很直接:重新注册 Butterfly 会调用的 cloudTags helper。
Butterfly 渲染标签页和侧栏标签卡片时,会调用这个 helper 生成标签链接。只要在页面生成前替换掉它,最后输出的 HTML 就会变成我们自己的版本。主题模板不用碰,node_modules/ 也不用碰。
脚本在 before_generate 阶段注册 helper:
1 | hexo.extend.filter.register('before_generate', registerCloudTagsWithCount) |
这个时机足够早。后面主题模板开始渲染时,拿到的已经是本站自己的 cloudTags。
自定义 helper 仍然接收 Butterfly 传进来的参数,比如 source、minfontsize、maxfontsize、limit、unit、orderby、order 和 page。这些参数本来就描述了标签来源、字号范围、排序方式和渲染位置。脚本没有再做一套配置,只是在原来的输入上改了输出。
流程大概是这样:
1 | 读取标签集合 |
最后每个标签会变成这样的结构:
1 | <a href="/tags/Hexo/" class="tag-cloud-item" style="font-size: 1.2em;" aria-label="Hexo: 3 posts">Hexo<sup>3</sup></a> |
文章数量直接来自 tag.length。在 Hexo 的 tag model 里,length 就是这个标签关联的文章数量。脚本把它存成 count,再写进 <sup>。
标签名会先过一遍 escapeHtml。标签通常是自己写的技术名,看起来很安全,但它最后会进入 HTML 文本和 aria-label。把 &、<、>、" 和 ' 先转义掉,是一个很便宜的保险。
字号处理这里稍微分了一下场景:
1 | const size = page === 'tags' |
当 page === 'tags' 时,说明现在渲染的是 /tags/ 主标签页。这里统一使用最小字号,让主页面更像一个整齐的标签索引。侧栏标签卡片则继续按文章数量计算字号。读者已经能直接看到数字了,所以字号只做辅助提示,不再承担全部信息量。
为什么不用客户端脚本补数字
给标签后面补文章数,也可以用浏览器端 JavaScript 做。例如页面加载后扫描 .tag-cloud-item,再根据某个数据源把数字插进去。
我没有选这个方案。原因很简单:构建期已经知道的信息,就别拖到运行时再补。
用 helper 在构建期输出完整 HTML,省掉了很多小麻烦:
- 输出 HTML 本身就是完整的,禁用 JavaScript 时也能看到文章数。
- 搜索引擎和无障碍工具拿到的是最终内容,不需要等待客户端脚本执行。
- 不需要额外维护前端数据源,也不需要考虑页面切换后的重复初始化。
- 与 Butterfly 原有模板调用保持一致,改动范围集中在一个 Hexo 脚本里。
静态博客本来就适合这样处理。标签数量、文章数量、链接地址都是生成时的确定信息,直接写进页面最省心。
用 CSS 压住视觉噪声
HTML 输出改变之后,CSS 也需要跟着调整。否则 <sup> 计数可能会继承主题原来的 badge 样式,导致位置、边框或背景不符合预期。
本站的自定义样式放在 source/css/custom.css,并通过 _config.butterfly.yml 的 inject.head 引入:
1 | inject: |
主标签页的选择器是 .tag-cloud-list a.tag-cloud-item。这里把标签改成轻量 pill:
- 加一点
margin和padding,让标签之间有稳定间距。 - 使用浅蓝色半透明背景和边框。
- 强制
color: var(--font-color) !important,避免主题随机色让页面过于跳跃。 - hover 时只增强边框、背景和轻微阴影,不做夸张动画。
对应的计数上标单独处理:
1 | .tag-cloud-list a.tag-cloud-item sup { |
这里最重要的是把 sup 拉回普通内联布局。position: static、透明背景、去掉边框和固定高度,都是为了避免它变成一个单独的 badge。文章数只是标签文本的一部分,不需要再做成一个抢眼的小按钮。
侧栏标签云使用的是 .card-tag-cloud a.tag-cloud-item。侧栏空间更窄,所以样式更轻:
display: inline,让标签自然排成文本流。- 去掉额外 padding。
- 使用灰色文本和较高行高。
- 计数上标使用更小字号和浅蓝色。
这样主标签页像索引,侧栏像快捷入口。两处 HTML 一样,CSS 根据上下文把它们调成不同的气质。
暗色模式适配
主标签页的 pill 背景在暗色模式下会显得偏淡,所以额外加了两条规则:
1 | [data-theme='dark'] .tag-cloud-list a.tag-cloud-item { |
这里只调整主标签页。侧栏标签本来就更接近普通文本,跟着全站字体颜色走即可。
颜色继续沿用站点现有的浅蓝色系,没有再加新主色。标签云应该融进页面,而不是突然变成另一套设计。
覆盖主题样式时要注意权重
这类定制最容易踩的坑是 CSS specificity,也就是选择器权重。
Butterfly 的样式里有不少带 ID 或上下文限定的选择器。自定义 CSS 虽然通过 inject.head 在主题样式之后加载,但如果选择器权重不够,仍然可能压不住主题原样式。
所以这次没有只写:
1 | .tag-cloud-item sup { |
而是区分主标签页和侧栏:
1 | .tag-cloud-list a.tag-cloud-item sup { |
这样权重够用,影响范围也更明确。以后如果主题在别处也复用 .tag-cloud-item,这段样式不至于到处乱飘。
少数颜色规则用了 !important,主要是为了压住主题的随机色和 hover 色。这里不是追求“写 CSS 爽一下”,而是因为这次本来就想把标签云的颜色收回来。
最后的效果
当前构建后,主标签页和侧栏都会输出带文章数的标签链接,大概长这样:
1 | Hexo<sup>3</sup> |
两处表现不一样:
/tags/主页面:统一字号,浅蓝 pill,适合完整浏览标签集合。- 侧栏标签卡片:文本流排布,保留轻微字号差异,适合快速跳转。
这次改动很小,但我挺喜欢这种改动。它没有改变站点结构,也没有引入新的依赖,只是把“这个标签下面有几篇文章”放到读者一眼能看到的位置,并顺手让标签云安静一点。个人博客维护久了,会越来越在意这种小地方。
以后标签数量真的多起来,再考虑给 /tags/ 页面加分组、搜索或按字母排序。现在先这样,够用,也不重。






