博客是从十月初开始陆陆续续改的,最近几天集中做了一次大翻新:从"主题默认皮"改成了现在这个样子。这篇不写教程,只记录一下改了什么、为什么这么改、踩了哪些坑,顺便把藏在角落里的几个小彩蛋交代清楚。
🎨 先定一个调子:薄暮花海
一开始的想法很朴素——“背景好看点就行”。试了几版都不太对:纯色太素,照片平铺又太吵。最后定下来的方向叫「薄暮花海」:
- 薄暮:深靛蓝 → 雾紫 → 雾粉的渐变夜空,叠一层缓慢漂移的柔光光斑
- 花海:飘落的花瓣 / 叶片,以及首页那条"走到哪算哪"的花海进度
关键原则是不额外请求任何图片:渐变、光斑、花瓣全是 CSS 和 SVG 画的,首屏几乎零成本。
| |
配上玻璃拟态卡片(半透明 + backdrop-filter + 细描边 + 柔和投影),标题换成衬线体(宋体系),正文保持黑体——文学感和可读性各退一步,刚好。
🧭 导航:从左侧栏搬到顶部胶囊
主题原本是左侧栏 + 右侧小部件。我把它换成了顶部胶囊导航,两侧小部件全部收进导航和页脚。
这里有个小细节:主题的 JS 依赖 #toggle-menu、#main-menu、#dark-mode-toggle 这三个节点,覆盖布局时要把它们保留下来,否则汉堡菜单和明暗切换会莫名其妙失效——这大概是改主题时最容易翻车的地方。
首页做了全屏 Hero:
- 照片直接当整页背景(
data-src懒加载 + 预加载下一张) - 居中站名「huanhe」,配一行手写体
- 语录逐字打出、逐字退格,像打字机一样
- 每次进首页随机选一张照片、随机挑一条语录,所以刷新几次每次都不一样
- 往下滚动,背景会渐进模糊,把视线让给正文
文章列表改成了 3 / 2 / 1 列的响应式网格,卡片高度自动对齐。
🌸 季节主题:春天和秋天不是一回事
给整站做了一套季节配色:按月份自动切换(3–5 春、6–8 夏、9–11 秋,其余冬),也支持用 ?season=spring 这样临时预览。
做法是把颜色全部收进 CSS 变量,一处切换全站生效:
| |
飘落装饰也跟着季节换:春樱瓣 / 夏绿叶是 SVG 剪影(同系三种颜色随机),秋枫叶 🍂 / 冬雪花 ❄️ 直接用 emoji。
这里有个我自己推翻的决定:一开始手机上直接关掉了飘落效果,怕卡。后来在手机上刷自己博客,觉得少了点意思,就改成"少而小"——桌面 34 片,手机 14 片、尺寸打八折,跨断点还会自动重建。
另外手机浏览器的地址栏会收放,
100vh并不可靠,所以高度用了vh和dvh双写,支持dvh的浏览器自动用更准的那套。
🗺 花海进度:9 / ∞ 个地方
首页有一张卡片,记录我走过的地方:临沂、图书馆、南大、海尔、幂律、洛阳、西安、北京、洛杉矶。
数字不是写死的,来自 data/places.yaml:
| |
显示成「9 / ∞ 个地方」——分母是无穷大,因为地方是走不完的。已点亮的地方在进度条上开出一朵朵小花,整条光带是流动的,永远走不到头,倒也挺符合"去看花海的路上"。
花海专题页现在是空的,只写了一行「待开发」。原因是我对两版自动生成的地图都不满意——这一页我想自己画。地图画好之后,把 places.yaml 里的坐标点上去就行。
🐛 小彩蛋:一条会漏虫子的拉链
导航框下方挂了一条拉链和一个小挂件。左右拖动挂件,拉过的地方链齿会岔开,然后……漏出小虫子(三只 Q 版小家伙随机一只,掉到屏幕底下就自己消失)。
快速拖动的时候会有个坑:
- 第一版用"绝对位置“记账——快拉时一次跳过一大段,中间那段既没漏虫、也永远不会再漏,表现就是"漏一半就停”
- 而且滴漏是靠
requestAnimationFrame驱动的,在没有渲染帧的环境里干脆一动不动
后来改成了三个东西配合:
- 按拉开距离记账(每 26px 一只),快速大步拖动也不会漏掉距离
- 令牌桶限速,突发有上限、持续有速率,性能可控
- 定时器滴漏,松手之后继续把这段拉链漏完
| |
顺手加了个互动:点一下虫子就能把它按掉(冻结在当前下落位置 → 缩小淡出)。
🏷 手绘风:贴纸标签和框格
导航框下面粘了四个"贴纸":GitHub、哔哩哔哩、邮箱、微信。手绘描边 + 每张略微倾斜,鼠标移上去会"扶正"一点。邮箱和微信点一下就是复制,不用手动选中。
配合的还有一套「手绘框格」样式:纸张质感底 + 不规则圆角 + 双层描边,用在评论区和一些卡片上,跟夜景渐变意外地搭。
手机上这四个贴纸会挤,所以窄屏(≤560px)只留图标,去掉文字标签就舒服了。
🎵 播放器:从"资源加载失败"到自己的播放条
原来用的是网易云的外链 iframe 播放器,图省事。结果经常弹一句:
资源加载失败
排查下来是第三方 iframe 在跨站环境里自己加载不了资源。于是干脆自己做了一个播放条,只借它的外链音频地址:
| |
现在的播放器:▶ / ⏸、进度条点哪跳哪、上一首 / 下一首、播放时 ♪ 音符从按钮里跳出来;整页刷新会接着上次的位置播。
歌单也不再写死了。构建时用脚本抓一次公开歌单,只保留真的能播的歌:
| |
VIP 歌曲匿名播不了,脚本会自动跳过——所以歌单里加 VIP 歌是看不到的,不是坏了。
踩到最深的坑:
<script>里 Hugo 会自动把字符串渲染成合法的 JS 字面量,我多套了一层jsonify,结果歌曲 ID 变成了带引号的字符串:
1 2请求地址:?id=%222724791369%22.mp3 ← 引号被转义进去了 网易返回:MEDIA_ELEMENT_ERROR: Format error去掉那层
jsonify就好了。这个坑很隐蔽——页面看起来一切正常,只有播放器默默不响。
切页面音乐不能断
旧方案还有个致命问题:点一下链接整页重载,播放器被销毁,音乐戛然而止。
解决办法是把站内跳转改成 PJAX:只替换内容区,导航(以及导航里的播放器)原地不动。
| |
换页后要顺手同步标题、body 类名、菜单高亮,还要把新内容里的 <script> 重新执行一遍。
⚠️ 代价是页面脚本必须幂等:反复进出首页不能叠加定时器和监听器。我的处理是给首页脚本发一个 token,旧循环自己退出;花瓣层重建前先清掉旧的;“回到顶部"改成事件委托 + 只注册一次。要是哪个链接不想走 PJAX,加个 data-no-pjax 就行。
💬 评论区和一点隐私
评论用的是 giscus(基于 GitHub Discussions),外面套了玻璃卡片和一句「留下一句话」的衬线标题。评论框的配色也自己托管了两套 CSS,跟着站点主题走,不再依赖第三方的默认皮肤。
邮箱和微信号我做了个防爬处理:不在源码里写明文,改成 Base64 存放,点击复制的那一刻才解码。
| |
说白了这只能挡住"扫源码的笨爬虫”,防不了有心人——但聊胜于无,至少不会第二天就开始收垃圾邮件。
⚡ 性能:把"好看"的代价压下去
动效和图片是最容易把首屏拖垮的两样东西,所以专门收拾了一轮:
| 项目 | 之前 | 现在 |
|---|---|---|
| 首页照片 | JPG | WebP 1600×900,体积小 54% |
| 角落里的吉祥物 | PNG 237KB | WebP 17.8KB(省 93%) |
| 首页 HTML | — | gzip 后约 12KB |
| 静态资源缓存 | 默认 | 图片 30 天、带 hash 的 JS/CSS 一年 immutable |
| 服务器传输 | 默认 | 开了 gzip(压缩级别 5,兼顾 CPU) |
另外所有动效只用 transform 和 opacity,并且尊重系统的「减少动态效果」设置——开了这个选项,动画会安静下来,只剩淡淡的状态切换。
🚀 部署:双击一下就完事
部署链路是 push → GitHub Actions 构建 → rsync 同步到服务器,本地只留一个 publish.bat(add / commit / push),不用记命令。
有个小机关:如果本地没有任何改动,它会自动建一个空提交,用来"纯刷新"站点。因为歌单是构建时抓的,我在网易云加了歌,本地文件其实一个字节都没变——没有这个空提交,git push 只会回一句 Everything up-to-date,什么都不会发生。
另外挂了个定时任务每 2 小时重新构建一次,顺手把歌单刷新了,不想等就手动双击一次。
服务器这边也在搬家的路上(换机房,备案接入审核中)。搬家期间老机器继续服务,域名不解析到新机器——这是备案的硬规矩,急不得。
📝 写在最后
这一轮改下来最大的体会是:静态博客也能做出"活着"的感觉。
不需要后端,光靠 CSS 变量、transform 动画、一点点原生 JS,就能有四季、有随机、有能玩的拉链、有不间断的音乐。代价是每个动效都要想清楚"它在低端机上要花多少",这也是我中途推翻好几个效果的原因——好看是加分项,卡顿是减分项,而后者的权重更高。
接下来想做两件事:
- 把花海地图自己画出来,让那 9 个地方真正落在图上
- 继续补细节:Hero 文字的对比度、分享卡片、响应式图片
改站如改人,慢慢来。
本站用的还是 Hugo + Theme Stack,所有改动都在站点级覆盖里,没动主题源码——这样以后升级主题不会把这一堆自定义全冲掉。
