把一年、一个月和一周放进三条进度条
做定制项目的时候,时间通常以交付日期存在:还有几天要上线,今天必须完成哪一部分,下一轮反馈什么时候回来。
项目告一段落以后,我想在博客里放一种没那么紧张的时间提醒。它不催我完成任务,只把一年、一个月和一周走到哪里,用三条进度条安静地显示出来。

三条进度,代表三种尺度
组件放在右侧栏,依次显示:
- 本年已经过去的比例,以及还剩多少天;
- 本月已经过去的比例,以及还剩多少天;
- 本周已经走到哪一天,以及周末前还剩多少天。
下方再单独留一块区域,显示距离下一个节日还有多久。最初这里只保留元旦、劳动节和国庆节,结果到了 2026 年 7 月,组件直接把“下一站”指向了国庆节,漏掉了 9 月 25 日的中秋节。
现在的节日表分成三类:
- 国内节日与纪念日,例如春节、清明、劳动节、国庆节;
- 农历传统节日,例如元宵、端午、七夕、中秋、重阳和除夕;
- 常见国际节日,例如情人节、复活节、母亲节、父亲节、万圣夜、感恩节和圣诞节。
这样做以后,“全面”不再等于堆一串当年日期。公历固定节日、按星期计算的节日和农历节日分别使用不同算法,第二年仍然能够继续工作。
日期计算最怕看起来差不多
本月进度最简单:获取今天是几号,再除以当月总天数。
本年进度需要考虑闰年:
周进度则要处理 JavaScript 中星期日等于 0 的情况。我把星期一到星期日统一映射成 1 到 7,这样进度条和“还剩几天”的文案更直观。
另一个容易忽略的问题是夏令时。直接用两个本地时间相减,在采用夏令时的地区可能遇到一天不是完整 24 小时的情况。计算天数时,我先把年月日转换到 UTC 的零点,再用毫秒差换算,避免时区偏移让倒计时多一天或少一天。
不需要每秒重新计算
这块组件展示的最小单位是“天”,没有必要像时钟一样每秒甚至每分钟更新。
页面加载时计算一次,然后只安排一个定时任务,等到下一个本地零点重新刷新。组件被移除时,定时器也会清理:
这种写法对 Firefly 的无刷新页面切换更友好。它不会因为在首页和文章页之间来回跳转,就留下越来越多的后台任务。
组件结构与进度条样式
组件文件为:
每一行由百分比、说明文字和进度轨道组成:
我没有直接使用原生 ,而是用普通元素控制宽度。这样渐变、发光和过渡动画更容易统一:
展开收起
脚本更新时只需要修改文字与 width:
计算年、月、周进度
核心更新函数可以按下面的顺序组织:
再分别传给 setProgress:
三类节日要用三种算法
固定公历日期最直接,月份仍然从 0 开始:
母亲节、父亲节和感恩节不是固定几号,而是“某月第几个星期几”。我写了一个通用函数:
上面的 0 代表星期日,4 代表星期四。复活节的规则更特殊,组件使用 Gregorian computus 算法单独计算。
农历节日没有硬编码成 2026 年的公历日期,而是借助浏览器自带的中文农历日历:
脚本从今天开始向后扫描 400 天,将每天转换成农历月日,再和节日表匹配。这样中秋始终匹配农历八月十五,端午始终匹配五月初五,不需要每年手动改日期。除夕不能写死为腊月三十,因为有些年份腊月只有二十九天;我的判断方式是检查“明天是否为正月初一”,如果是,今天就是除夕。
最后把三类候选日期合并、去重并排序,取第一个不早于今天的日期:
2026 年 7 月 24 日之后,完整列表会先显示 8 月 19 日的七夕节,随后是 9 月 25 日的中秋节,不会再直接跳到国庆节。
注册并放到右侧最下面
与其他侧栏组件一样,需要完成三步:
- 在
WidgetComponentType中加入"scheduleProgress"; - 在
SideBar.astro导入组件并写入componentMap; - 在
rightComponents最后加入配置。
它被放在右侧配置最后,因此会出现在日历与其他启用组件之后。position: "sticky" 让它属于跟随滚动区域;如果改成 top,它会和时钟、统计等固定顶部组件排在一起。
测试时重点看月末、年末、星期日和闰年二月。日期组件最容易在边界条件出错,普通工作日显示正确并不能说明算法已经完整。
进度条不是为了制造焦虑
把时间做成百分比,很容易让人产生“今年怎么已经过去这么多”的紧迫感。所以我没有使用警告色,也没有加入“剩余生命”一类过重的文案。
三条进度都沿用博客主题色,数字保持小尺寸,下方倒计时只写“距离下一站”。它更像一个路标,而不是考核表。
有时我会觉得时间很快,电脑里的项目文件夹一批接一批,回过头才发现一个月已经接近尾声。把这些尺度放在侧边栏,至少能让我在打开博客时短暂地意识到:今天属于这一周,也属于这个月和这一年。
工具提醒我把工作做完,进度条则提醒我,时间本身也值得被看见。