本站是怎么魔改出来的
如果你刚看完上一篇《用 Hexo 搭建属于自己的 Blog》,那你现在有一个能跑起来的博客。
这篇继续往下,讲我这个站后来加的那些东西——美化、Bing 壁纸、音乐墙、春节灯笼、健身打卡。每一块都尽量做到”复制三个文件就能用”。
难度比上一篇高,但也没多高。你不需要懂很深的 JS,按顺序抄 + 改路径就行。
改造的总原则
先把原则摆在前面,不然越改越乱。
原则一:不动主题源码
主题放在 node_modules/hexo-theme-fluid/ 里,改了就升不动主题了。所有自定义都走 _config.fluid.yml 里的 custom_css / custom_js / custom_head,加文件不改文件。
原则二:样式全走 CSS 变量
一个站点颜色、字体、阴影、圆角这些东西出现的地方太多。把它们集中到一个 :root 里当作设计令牌(design tokens),其它地方只引用变量。想换风格的时候只改一处。
原则三:自定义页面 = markdown + 自定义 JS + 自定义 CSS
每个”花活页面”都是这个套路:一个 .md 负责写 HTML 结构,一个 .js 负责交互,一份 .css 负责样式。后面音乐墙、健身打卡都是这么做的。
一、挂载自定义样式和脚本
先把”挂”的动作做对。打开 _config.fluid.yml,加这一段:
1 | |
Fluid 会把 /js/xxx.js 和 /css/xxx.css 映射到 source/js/ 和 source/css/ 这两个目录。也就是说,把文件放到 source/js/ 或 source/css/,它就会被拷到产物里。非常适合用来塞自定义代码。
没有这两个目录的同学先建好:
1 | |
二、设计令牌 · 一切的底座
新建 source/css/custom.css,最顶上写这一段:
1 | |
这几行看起来没啥效果,但整个站之后所有的卡片、按钮、阴影、边框都会从这里取值。以后想换风格,改这一段就行,不用全局搜索替换。
三、Nord 配色 · 覆盖主题颜色
Fluid 自己提供了一组变量让你在 _config.fluid.yml 里改:
1 | |
刷一下:从暖色默认变成冷静的 Nord 雪原风,Banner 上任何壁纸都压得住。
四、Bing 每日壁纸 Banner
默认首页 banner 是一张静态图,太无聊。做成每天自动换 Bing 首页壁纸:
新建 source/js/bing-banner.js:
1 | |
几个做对的小事:
- localStorage 缓存:当天只请求一次接口,之后从缓存读
- 预加载再切:如果直接
background-image: url(...),图还在下载时背景是白的,闪一下很难看 - 失败降级:Bing 接口挂了切回本地图片,不会开天窗
- 早 7 点换图:半夜打开博客还是上一张壁纸,早上起来才换,避免凌晨写文章时壁纸突然变了
五、音乐墙
这个模块最有意思,做成”唱片墙 + 播放器”。不用任何 JS 库,原生 HTML5 <audio> 够用。
5.1 准备音乐文件
在 source/music/ 下塞音频和封面:
1 | |
Hexo 会把整个 source/music/ 原样拷贝到产物。音频文件直接可以用 /music/audio/xxx.mp3 访问。
5.2 页面结构
source/music/index.md:
1 | |
两个要点:
是必须的。Hexo 的模板引擎会尝试解析{{ }},包在 raw 里面防止它乱搞- 歌单数据
window.MUSIC_LIST直接写在页面里,这是最简单的做法。后续想做”改歌单不用改 HTML”可以把它拆到/music/list.json
5.3 交互逻辑
source/js/music-wall.js 完整版比较长,核心骨架:
1 | |
完整版要做的细节有:
- 封面旋转动画:正在播放的卡片加
.is-playing,CSS 里animation: music-spin 18s linear infinite - 脉冲光圈:
::after+box-shadow扩散,再加keyframes淡出 - 进度条拖动:用
pointerdown / pointermove / pointerup三件套,拖的时候只动 UI,松手才真的audio.currentTime = ...。不这么做的话会听到拖拽过程中音频不断抖 - 键盘可达:进度条按 ← → 快退/快进 5 秒,Home / End 跳到头尾
- 自动下一首:
audio.addEventListener('ended', next)
5.4 样式(节选)
几个关键 CSS 片段:
1 | |
三个视觉重点:转的唱片、脉冲光圈、玻璃态播放器。缺一个就不像了。
5.5 挂到菜单
最后一步,让访客能进到这个页面。_config.fluid.yml 导航里加一项:
1 | |
六、春节灯笼(时间窗口内才出现)
这是个彩蛋。只在除夕前 1 天到正月十五期间自动挂 4 只灯笼,其它时间 0 副作用。
source/js/spring-festival-lanterns.js:
1 | |
关键设计:
- 开头
if (!shouldShow()) return,不在春节窗口直接退出。完全没有 DOM 和 CSS 被插入,其它时间的用户拿到的就是一个空函数 - 灯笼 HTML 纯
<div>拼,用 CSS 画椭圆和流苏,不用图片 - 摆动动画
@keyframes deng-swing加prefers-reduced-motion媒体查询,晕动症用户会自动暂停动画
七、健身打卡(markdown 当数据库)
这是我个人最得意的一块。写作是 markdown,前端是 JSON——中间有个构建期 generator 把两者打通。
7.1 数据源
在 source/_fitness/ 下每天一个文件(下划线开头,Hexo 不会把它们渲染成独立页面):
1 | |
每个文件就是一次打卡:
1 | |
7.2 构建期 generator
Hexo 允许你在 scripts/ 下写自定义生成器,在 hexo generate 执行时被自动加载。
新建 scripts/fitness-data.js:
1 | |
执行 hexo generate 后,public/api/fitness.json 就有了所有打卡数据。
7.3 前端直接 fetch
前端页面 source/fitness/index.md 只负责 DOM 骨架:
1 | |
配套的 source/js/fitness-wall.js 拉 JSON,按年份分 Tab、按月分组渲染。代码不贴了,全文在 source/js/fitness-wall.js。
加一条打卡的完整流程:
1 | |
八、构建期抓远程数据(十年留言墙)
上面健身打卡读的是本地文件。再进一步:构建期去远程接口拉数据,落盘成静态 JSON。
为什么要这么做?两个问题同时解决:
- CORS 问题消失:前端只读自己域名下的
/api/xxx.json - 源站挂了也不影响:上次构建的数据还在,只是不更新
scripts/ten-year-wall.js 的样板(去掉具体接口):
1 | |
三个细节:
- 环境变量跳过开关
TEN_YEAR_WALL_SKIP=1:离线构建、网络抽风时能兜底 - try/catch 包到最外层:失败不能让整个
hexo generate挂掉 - 空负载也要写文件:
fetch失败就写空数组,前端只看到”数据为空”,不会 404
这个套路非常通用,任何”博客里想嵌外部数据但又怕依赖”的场景都能套。
九、几个小但很爽的增强
这些都是一行 / 几行的事,不值得单独列,但加起来很提气。
iciba 每日一句当 slogan
首页那句副标题别写死,拉英语趣配音的每日一句 API,每天换。source/js/iciba-slogan.js 里 fetch 一下,拿到中英文扔给 Fluid 的 typing 插件打字机。
站点运行时长
页脚加个”本站已运行 N 天”,不蒜子做不到(它只有 PV/UV)。自己写:
1 | |
外链 nofollow
避免 SEO 权重被别人的站吸走。_config.yml:
1 | |
依赖 hexo-filter-nofollow。
产物压缩
HTML / CSS / JS 构建期压缩一次,线上省流量:
1 | |
依赖 hexo-minify。
十、最终的目录长这样
走到这里,整个博客的结构应该是:
1 | |
新增一个自定义页面的标准流程:
source/xxx/index.md写 HTML 骨架,挂一个<script src="/js/xxx.js">source/js/xxx.js写交互source/css/custom.css里追加样式- 数据复杂的话在
scripts/下写一个 generator 吐 JSON 到public/api/xxx.json _config.fluid.yml菜单里加入口
跟着这个模板做,加一个新模块的时间大概是几个小时到半天。
最后
这些东西单看都不难,但加起来是一个 “属于你自己” 的博客。别人用 Fluid 的站和你用 Fluid 的站,一眼就能看出区别——区别不在主题,在这些散落在角落的小心思。
我知道很多人搭完博客就再也不更新了。这挺正常。博客的乐趣一半是写,一半是折腾。哪个都能让你高兴一阵。
本站所有这些改造的源代码都在 GitHub 仓库 里公开(源码在另一个仓库,链接在 about 页)。欢迎 fork、抄、改。