把日记装进日历,把目录变成地图。

保险箱里的日记我基本上每天都会更新,内容自然越来越多。最开始它只有一个普通的目录(TOC),几十篇日记堆在一起之后,想找"上周三写了什么"基本靠肉眼滚动——这不可接受。

所以我做了一个日记日历导航:一个悬浮在屏幕底部居中的胶囊,把整篇日记的日期结构装进去,随时呼出、随时跳转。它只依赖一个 JS 文件和一个 SCSS 文件,零外部依赖,支持深浅色模式。

今天把这个功能的完整设计、使用方法和踩过的坑记录下来,方便以后复盘,也给想做类似功能的朋友一点参考。

本文同时作为功能介绍和实现复盘。只想知道自己怎么用的话,重点读「二、使用方法」;想抄一份到自己博客的,请重点阅读「三、实现原理」和「四、踩坑记录」。

一、功能总览 链接到标题

打开带日记的文章后,屏幕底部居中会出现一条胶囊:

[ 📅 ] [ 2026年 ] [ 8月 ] [ 28日 ]
部件 功能
📅 日历按钮 弹出迷你月历:有日记的日期用主题色点亮,今天有一圈描边,‹ › 按月翻页(范围自动限定在有日记的月份)
年 / 月 / 日 分段 各自弹出一列单独的列表,按级联方式筛选;选了年,没有日记的月份自动变灰
胶囊上的日期 实时跟随阅读位置:往下滚,它显示你正在读的那一天的日期
点击日期 平滑滚动到那天的日记,标题对齐到视口最顶端,并有一段 4 秒的彩色高亮慢慢淡出

几个设计上的细节:

  • 面板打开时,只有被点的那一段高亮,并且面板吸附在对应按钮的正上方(迷你月历则与胶囊中心对齐)
  • 点击面板外部或按 Esc 关闭;跳转后面板自动收起
  • 保险箱文章在锁定状态下胶囊自动隐藏,解锁后出现
  • 只在存在日记日期标题的页面上启用,其他页面完全无感

二、使用方法 链接到标题

2.1 怎么让它出现在文章里 链接到标题

不用配置。在任意文章(posts 或保险箱都行)的正文里写日期标题:

### 2026年8月28日

今天的内容……

页面加载时脚本会自动扫描正文里所有 X年X月X日 格式的三级标题,动态生成年/月/日索引。以后每天追加一篇,日历自动点亮新日期,翻页范围自动扩展——写日记的人不需要做任何额外操作。

注意标题格式:X年X月X日 要连写开头、半角数字。### 2026年9月 或 ### 2026-09-15 这类写法是识别不到的。

2.2 怎么单独关闭 链接到标题

如果某篇文章碰巧有长得像日期的标题、但你不想要这个功能,在 front matter 里加一行:

+++
calendar = false
+++

实现方式是布局输出一个 <meta name="diary-calendar" content="off">,脚本检测到就跳过初始化。


三、实现原理 链接到标题

3.1 核心思路 链接到标题

整个功能一个 JS 文件(assets/js/diary-calendar.js)加一个 SCSS 文件(assets/scss/diary-calendar.scss),核心思路三句话:

  1. 扫描:页面加载时扫描正文里匹配日期格式的 h3 标题,建立"日期 → 标题元素"的索引
  2. 渲染:根据索引生成底部胶囊和三种弹出面板(迷你月历 / 年列表 / 月列表 / 日列表)
  3. 跳转:点日期 scrollIntoView 过去,加一个 CSS 动画类做高亮

没有任何构建期依赖——因为 Hugo 是静态生成器,文章内容在运行时才能被 JS 完整感知,所以"扫描"放在浏览器端做,写多少日记都不需要重新配置。

3.2 与主题深浅色模式的打通 链接到标题

hugo-coder 主题用 SCSS 变量 + body 上的 colorscheme-dark/auto 类来换肤,并不存在 CSS 变量。所以先建了一个 colors.scss,把组件用到的颜色定义为 CSS 变量,并在两套主题类下分别赋值:

body.colorscheme-dark {
  --bg-color: #212121;
  --link-color: #42a5f5;
  /* ... */
}
body.colorscheme-auto {
  @media (prefers-color-scheme: dark) { /* 同上 */ }
}

组件里所有颜色一律 var(--xxx, fallback) 写法,深浅色切换就自动生效了。

3.3 尺寸自适应策略 链接到标题

组件尺寸全部走 clamp(下限, vw理想值, 上限):

width: clamp(25rem, 30vw, 34rem);   /* 月历面板:屏大更大、屏小更小 */
font-size: clamp(1.4rem, 1.5vw, 1.7rem); /* 胶囊字号同理 */

连续缩放:窗口拖动时逐像素变化,没有断点跳变;手机上另有媒体查询覆盖成手工调优过的紧凑值。面板的移动范围也做了约束(max-width: calc(100vw - 10rem)),永远不会盖住右下角的"返回顶部"按钮。


四、踩坑记录 链接到标题

功能不大,坑一个接一个,记录几个印象最深的:

1. 翻月面板"闪退"。点击 ‹ 翻月时面板会重渲染 DOM,此时点击事件还在向上冒泡——等它冒到 document 上的"点击外部关闭"监听器时,原来的按钮已经脱离文档,contains() 误判为"点了外部",面板当场关闭。解决:判断 event.target.isConnected,目标已被移出文档时跳过关闭逻辑。

2. 手机上高亮不显示。高亮动画的背景色用了 color-mix(),这个函数需要 Safari 16.2+,而 iPhone 7 最高只能跑 iOS 15——无效语法整条被丢弃,动画只剩"从透明到透明"。解决:改用 CSS 变量 + 预混好的 rgba 颜色,新旧浏览器通吃。

3. 星期和数字对不齐。星期行和日期网格是两个独立的 7 列网格,列宽写 1fr 时它实际是 minmax(auto, 1fr)——iOS 给 <button> 的默认内边距会把单元格最小宽度撑大,日期网格于是比星期行宽,横向溢出。解决:repeat(7, minmax(0, 1fr)) 强制等分 + 重置按钮默认内边距。

4. 胶囊折行。窄屏上 Flex 把按钮压缩,“2026年"被拆成"2026"和"年"两行。解决:white-space: nowrap + flex: 0 0 auto,再配合 clamp() 让字号随屏宽收缩。

5. 隐藏属性失效。解锁面板用的 hidden 属性会被组件自己的 display: flex 覆盖(作者样式优先级高于浏览器对 [hidden] 的默认规则)。解决:显式补一条 [hidden] { display: none }。

6. libSass 不认现代 CSS。Hugo 内置的 SCSS 编译器会把 min(92vw, 34rem) 当成自己的函数求值,遇到混合单位直接报错;span& 这类后缀父选择器也不支持。解法都简单:改用等价的老写法。


五、文件清单 链接到标题

文件 作用
assets/js/diary-calendar.js 扫描日期标题、渲染胶囊与面板、跳转与滚动跟随
assets/scss/diary-calendar.scss 胶囊、面板、月历的全部样式
assets/scss/colors.scss 站点配色 CSS 变量(深浅色两套)
hugo.toml 在 customJS / customSCSS 中注册以上两个文件

文章侧唯一需要关心的就是「二、使用方法」里的日期标题格式。


六、真实案例:本页就是活例子 链接到标题

下面的日期标题就是 ### X年X月X日——所以你应该已经注意到,屏幕底部出现了胶囊。点开 📅 或者日期列表测试跳转。

2026年8月27日 链接到标题

这一段用来演示"选中日期后跳转”:标题会对齐到视口最顶端,然后有一段 4 秒的蓝色高亮缓缓淡出。

2026年8月28日 链接到标题

每多写一个日期标题,日历里就自动多一个点亮的日子。不需要注册、不需要配置,日期索引是页面加载时实时扫描出来的。

2026年8月29日 链接到标题

胶囊上的日期会跟随你的滚动位置变化——往上滚回「一、功能总览」,它会切回更早的日期;再滚回这里,它又显示今天。


写在最后 链接到标题

现在日记是"写进日历"而不是"堆进目录":每天一格,一目了然,随手可跳。