前言
Eurkon 的文章头部很有辨识度,但它是 Butterfly 的深度分支,整套覆盖会增加升级成本。本次只移植文章头部这一项:封面渐变、分类标签、动态波浪、主题色和评论统计,其余仍使用当前 Butterfly。
一、为什么不直接修改主题模板
node_modules/hexo-theme-butterfly/ 中的文件会在重新安装依赖时被覆盖。如果直接改 Pug 模板,下一次 npm ci 或主题升级就需要重新合并。
这里采用“生成后增强”:
Butterfly 正常渲染文章 HTML |
核心文件都在主题目录之外:
scripts/post-header-effect.js |
二、怎样决定一篇文章是否启用
脚本默认效果名为 eurkon:
const effect = page.post_header_effect === undefined |
因此普通文章有封面就会启用。如果某篇文章不需要,在 Front Matter 中写:
post_header_effect: false |
文章还必须存在 top_img 或 cover:
const cover = page.top_img || page.cover |
没有图片时保留 Butterfly 原始头部,避免生成一个没有视觉主体的空效果。
三、如何在原头部上增加能力
过滤器先查找 Butterfly 已生成的头部:
const headerPattern = |
匹配后增加:
<header |
class 交给 CSS,data-* 交给浏览器脚本。构建阶段不需要知道图片最终应使用什么颜色。
长标题会额外获得类名:
const longTitleClass = |
使用 Array.from() 而不是普通字符串长度,对中文和部分 Unicode 字符更稳妥。
四、为什么封面要重新放进一个 img
Butterfly 原头部主要用 background-image。本效果额外插入一层:
<div class="eurkon-post-cover" aria-hidden="true"> |
独立的 <img> 更方便:
- 使用
object-fit控制裁切; - 通过伪元素叠加渐变;
- 在浏览器中读取像素并计算主色;
- 不影响 Butterfly 原来的页面背景兜底。
CSS 使用变量保存主色:
#page-header.post-header-eurkon { |
渐变、阴影和波浪都引用同一变量,换色时不必逐条改样式。
五、封面主色是怎样提取的
浏览器创建一个很小的 Canvas,把封面缩放到 48 × 48:
canvas.width = 48 |
缩小有两个目的:减少运算量,并让相邻像素自然合并,避免一块小细节决定整张图的主题色。
脚本不是简单求平均,而是先过滤不适合作为主题色的像素:
const brightness = (r * 299 + g * 587 + b * 114) / 1000 |
- 半透明像素跳过;
- 过黑像素会让头部发闷;
- 过白像素容易让文字失去对比度。
接着把 RGB 每个通道右移 5 位:
const key = `${r >> 5}-${g >> 5}-${b >> 5}` |
原本每个通道有 256 种值,右移后只剩 8 档。相近颜色会进入同一个桶,再按饱和度加权:
const weight = 1 + saturation / 96 |
得分最高的颜色桶成为主题色。比起纯平均值,这种方法更容易保留封面中有辨识度的颜色。
六、为什么有时自动取色会失败
Canvas 读取跨域图片像素需要图片服务器返回正确的 CORS 响应头。否则浏览器会把 Canvas 标记为“污染”,调用 getImageData() 时抛错。
脚本用 try...catch 兜底,并允许手工指定:
post_header_color: '#1677b3' |
运行时优先使用手工颜色:
const configuredColor = parseHexColor( |
因此远程图床暂时没有 CORS,也不会让整个头部脚本中断。
七、动态波浪怎样实现
页面插入一段 SVG,定义一条可重复使用的路径:
<defs> |
随后用四个 <use> 引用同一条路径,设置不同的纵向位置、透明度和动画时长:
<use href="#eurkon-gentle-wave" x="48" y="0"></use> |
CSS 只移动波浪组的横向位置,四层速度不同,就形成视差效果。对于设置了“减少动态效果”的用户,媒体查询会关闭动画:
@media (prefers-reduced-motion: reduce) { |
八、分类和标签怎样移到标题上方
构建阶段直接读取 page.categories 和 page.tags,生成带链接的导航:
const categoryLinks = categories.map(category => |
新的分类标签插到 #post-info 开头,再移除 Butterfly 原元信息行中的重复分类,避免同一信息出现两次。
九、Twikoo 评论数怎样加载
只有同时满足以下条件才输出评论数容器:
- 文章没有关闭评论;
- 当前评论系统是 Twikoo;
- 配置中存在 Twikoo
envId。
浏览器端按需加载 Twikoo 客户端,然后调用:
client.getCommentsCount({ |
本地预览时不会请求浏览量计数,避免刷新预览页面污染线上数据。接口失败时显示 —,而不是让加载动画永久旋转。
十、PJAX 为什么需要单独处理
Butterfly 切换文章时可能不刷新整个页面,所以初始化同时监听:
document.addEventListener('DOMContentLoaded', init) |
头部使用 data-post-header-ready 防止同一页面重复取色;全局监听器也用变量保证只绑定一次。
十一、维护入口
- 结构增强、分类标签和资源注入:
scripts/post-header-effect.js - 封面布局、渐变和波浪:
source/css/post-header-eurkon.css - 自动取色、Twikoo 和 PJAX:
source/js/post-header-eurkon.js - 单篇关闭效果:
post_header_effect: false - 单篇手工颜色:
post_header_color: '#1677b3'
结语
把独立功能移植到主题外部,比直接覆盖整个 Eurkon 分支更容易维护。核心思路是让 Butterfly 继续生成可靠结构,再用过滤器和运行时脚本补充效果。






