本文介绍博客主页新上线的苹果风格双时区网页时钟小组件,并分享它背后的设计取舍、时区计算和模拟表盘实现。
现在打开桌面版博客主页,可以在搜索框下面看到两个模拟时钟:左边是访客的“你的时间”,右边是站长的“我的时间”。界面没有“世界时钟”标题,也没有显示我的姓名或所在国家。
它看起来只是主页上的一个小装饰,真正动手之后却碰到了不少值得讨论的问题:浏览器怎样处理时区和夏令时、模拟指针如何计算角度、网站主题与表盘昼夜如何分离,以及一个实时变化的视觉组件怎样兼顾双语和无障碍体验。

可复用的源代码已经发布到 GitHub: Jingyuan-Zheng/apple-style-web-clock-widget。 仓库采用 MIT 许可证,包含可独立运行的演示、零框架 CSS 与 JavaScript、中英文说明、配置示例,以及时区和指针角度测试。
为什么选择苹果风格
这个组件的视觉语言明显受到苹果时钟界面的启发:圆形表盘、纤细指针、克制的数字、醒目的橙色秒针,以及带有柔和层次的圆角卡片。
我并不想逐像素复制某一个苹果应用,而是希望保留它最值得借鉴的部分:
- 不看文字也能立刻认出这是时钟;
- 两个时区属于同一组信息,但又能快速区分;
- 文字只负责解释必要信息;
- 阴影和材质服务于层次,而不是抢走表盘的注意力。
所以最后留下的可见文字只有 “你的时间” 和 “我的时间”。“世界时钟”这个标题其实没有增加新信息,姓名和国家也不是完成时间比较所必需的内容。删掉它们以后,组件反而更像一个安静地融入主页的系统小部件。
同一个此刻,两种墙上时间
时区功能最重要的原则,是两个表盘必须来自同一个瞬间,而不是各自独立计时。
每次刷新都先创建同一个 Date:
|
|
访客时区由浏览器提供:
|
|
另一个表盘读取网站预设的 IANA 时区。两者都交给 Intl.DateTimeFormat,再通过 formatToParts() 拆出年、月、日、时、分、秒。
这里没有简单地给当前时间“加几个小时”。固定写死 +1 或 +2 看似方便,一旦遇到夏令时切换就会出错。让浏览器根据 IANA 时区和当前日期计算,才能在不同季节得到正确结果。
怎样计算时差
显示两个时间并不难,真正容易出错的是下面那行“相差几小时”。
组件会把目标时区格式化后的年月日时分秒,暂时当作 UTC 时间重新组合:
|
|
得到目标时区相对 UTC 的分钟偏移后,再减去访客本地的偏移量:
|
|
我保留了小数时差的格式化,因为真实世界并不是所有时区都相差整数小时。有些地区相差半小时,甚至四十五分钟。一个世界时钟如果默认全世界都按整点划分,看起来简洁,实际并不可靠。
模拟指针其实是一道几何题
一圈是 360 度,因此每分钟和每秒对应 6 度,每小时对应 30 度:
|
|
时针公式里的 minuteAngle / 12 很重要。没有它,时针只会在整点突然跳到下一个数字;加上分钟带来的偏移后,时针才会随着时间平滑地走过两个小时刻度之间的位置。
每根指针本质上都是一个绝对定位的细长元素,旋转中心放在底部中央:
|
|
JavaScript 只更新角度变量,指针的长度、粗细、颜色和位置都留给 CSS。这样比每秒拼接一大段行内样式更清楚,也让视觉调整不必改动计时逻辑。
十二个数字则由 Hugo 模板循环生成。CSS 先把每个数字容器旋转到对应位置,再对子元素做反向旋转,让数字沿圆周排列却始终保持正立。
网站深浅色和表盘昼夜不是一回事
这个组件其实有两套互不相同的颜色逻辑。
外层卡片跟随博客的亮色或暗色主题,让它在右侧栏里看起来属于同一个网站;表盘本身则根据各自显示的时间判断白天和夜晚:
- 07:00–18:59 使用浅色表盘;
- 19:00–06:59 使用深色表盘。
这个区分是后来特别调整的。如果表盘也只跟随网站主题,那么用户把博客切到暗色模式后,即使某个时区正值白天,表盘也会变黑,原本有意义的昼夜提示就消失了。
现在两个表盘会分别检查自己的当地小时,因此一个浅色、一个深色是完全正常的状态,也正是世界时钟最直观的价值之一。
每秒重算,而不是自己数秒
组件使用 setInterval 每秒刷新一次,但它不会在上一次结果上简单加一秒。
每次触发都会重新读取当前 Date,再计算两个时区、时差和全部指针角度。浏览器把后台标签页的定时器暂停或延迟后,下一次刷新仍会直接回到正确时间,不会把延迟逐渐累积成走时误差。
同样的思路也能自然处理系统时间变化和夏令时切换。这里的定时器只是提醒界面重新绘制,真正的时间来源始终是系统时钟。
在 Hugo 里怎样接入
我把时钟写成一个独立的 Hugo partial,并在主页 widget 配置中把它放到搜索框之后。这样没有修改搜索组件本身,以后想调整顺序或移除时钟,只需要改 widget 列表。
整个实现分成三层:
- Hugo 模板负责结构、十二个数字和中英文标签;
- SCSS负责卡片、表盘、指针、阴影以及明暗状态;
- TypeScript负责时区解析、时差计算和每秒更新。
GitHub 开源版本去掉了 Hugo 依赖,并把相同思路整理成标准 data-* 属性。普通网页无需框架或构建工具,也可以直接嵌入和配置。
组件只出现在宽度不小于 1024 像素的桌面右侧栏,手机端不会显示,也不会占据正文空间。这不是“移动版还没做完”,而是有意的取舍。手机屏幕有限,导航和文章内容的优先级明显高于一个辅助性的时间对比。
双语和无障碍不能最后再补
会动的指针对屏幕阅读器没有意义,因此每个表盘都会获得一段实时更新的无障碍说明,包含表盘名称、数字时间和时差。页面上已经可见的时差文字则对屏幕阅读器隐藏,避免同一信息被重复朗读。
文字由当前 Hugo 语言决定:
- 英文使用 “Your time”“My time”“hour / hours”;
- 简体中文使用“你的时间”“我的时间”“小时”。
内部解析固定使用公历和拉丁数字,避免计算逻辑受浏览器显示语言影响;真正呈现给读者的标签再根据页面语言本地化。把计算格式和展示语言分开,双语实现会稳定很多。
这个小组件带来的心得
做完以后,我最大的感受是:小组件最需要的不是更多功能,而是更清楚的边界。
我先后去掉了组件标题、姓名、国家和移动端版本;又把网站主题与表盘昼夜拆成两套逻辑。时区规则交给浏览器,表盘绘制则尽量留在简单的 CSS 和几何计算里。
最后它只做一件事:用两个时钟完成一次时间比较。没有设置面板,没有城市列表,也没有复杂交互。正是这种克制,让它更像主页的一部分,而不是一个在侧边栏里争夺注意力的迷你应用。
越小的界面,设计和工程之间的距离往往越近。多一行文字、多一个颜色条件、多一个断点,都会立刻改变视觉结果。这次上线也提醒了我:所谓精致,很多时候并不是不断添加,而是认真决定哪些东西可以不出现。