最近在处理一个项目排期的时候,发现一个很实际的问题:团队里经常需要知道"距离某个截止日期还有多少天",但每次都要打开日历数日子,或者用命令行算时间差,实在不够直观。
问题背景
作为独立开发者,我经常需要跟踪各种截止日期------项目交付、域名续费、服务器证书到期等等。市面上确实有不少倒计时工具,但大多存在几个问题:
- 要么功能太复杂,打开页面就被各种设置项淹没
- 要么需要安装应用,杀鸡用牛刀
- 有的还带广告,体验很糟糕
我就是想要一个打开即用、设置目标时间就能看剩余时间的轻量工具。而且作为开发者,我希望能完全掌控代码,方便后续按需定制。
技术选型思考
一开始考虑过几个方案:
- 用现成的 npm 包 :比如
countdown库,但为了一个倒计时引入依赖,感觉有点重 - 写个命令行脚本:能用,但不方便给团队其他人用
- 做个网页工具:跨平台、无需安装、分享方便,但需要处理前端的一堆细节
最后决定做个单文件 HTML,纯 vanilla JS 实现。理由很简单:不需要构建工具,不需要服务器,双击就能打开,这符合"工具就该轻量"的原则。
AI 辅助开发过程
说实话,一开始我连 datetime-local 输入框的兼容性问题都懒得查,直接打开了 Claude 开始对话。
第一轮:需求描述
我给的 prompt 大概是这样的:
帮我写一个单文件 HTML 倒计时器,支持设置目标日期和时间、自定义事件名称、实时显示剩余天时分秒、每秒刷新,支持深色模式,中英文切换。
AI 很快就生成了一版,整体结构不错,但我发现几个问题:
问题 1:时间计算有 bug
AI 第一次用的是 Date.now() 和 new Date(targetStr) 直接相减,这在跨时区时会出问题。比如用户在东八区设置了一个 UTC 时间,显示的剩余时间会差 8 小时。
javascript
// AI 第一次的写法(有 bug)
var ms = new Date(targetStr) - new Date();
我的修正方案 :用 datetime-local 输入的值是本地时间字符串,直接 new Date(targetStr) 解析即可,但要注意 new Date('2024-01-01T00:00') 在不同浏览器解析行为可能不同,最好手动解析:
javascript
function parseLocalDateTime(str) {
var parts = str.split('T');
var dateParts = parts[0].split('-');
var timeParts = parts[1].split(':');
return new Date(
+dateParts[0], +dateParts[1] - 1, +dateParts[2],
+timeParts[0], +timeParts[1]
);
}
问题 2:i18n 实现太笨重
AI 第一次的国际化方案是每个元素都写 if (lang === 'zh') ... else ...,代码重复不说,还容易漏。我让它改成 key-value 映射 + t() 函数的方式,清爽多了。
问题 3:进度条逻辑没考虑边界
AI 写的进度条在"起始日期晚于目标日期"时会算出负百分比,需要 clamp 到 0-100。
第二轮:交互细节打磨
我发现 AI 生成的版本有几个交互问题:
- 页面标题不会更新 :我让它把倒计时同步到
document.title,这样浏览器标签页上就能直接看到剩余时间 - 快捷预设按钮是写死的 :比如"新年"预设,如果写死
2025-01-01,明年就失效了。我让 AI 改成动态计算:
javascript
// 动态计算下一个新年
function getNextNewYear() {
var now = new Date();
return new Date(now.getFullYear() + 1, 0, 1); // 明年1月1日
}
- 到期后的表现:我要求到期后切换为正计时(显示已过去多久),并变红提示。AI 一开始只是显示"已过期",不够直观。
第三轮:视觉和细节
设计上我提了几个要求:
- 数字用等宽字体(
monospace),避免每秒跳动时宽度变化导致布局抖动 - 数字切换时有简单的动画效果(用 CSS
transition) - 深色模式下所有颜色都要重新适配
AI 在这轮表现不错,CSS 变量切换深色模式的方式很干净:
css
:root {
--bg: #fff;
--text: #111827;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a2e;
--text: #e2e8f0;
}
}
核心实现细节
时间差计算
这是倒计时的核心,要注意:
- 不能用
Math.abs()直接取绝对值,因为天、时、分、秒需要分别计算 - 用
Math.floor()而不是Math.round(),否则会出现"59秒显示为60秒"的问题
javascript
function calcDiff(from, to) {
var ms = to - from;
var isPast = ms < 0;
ms = Math.abs(ms);
var d = Math.floor(ms / 86400000);
ms -= d * 86400000;
var h = Math.floor(ms / 3600000);
ms -= h * 3600000;
var m = Math.floor(ms / 60000);
ms -= m * 60000;
var s = Math.floor(ms / 1000);
return { d, h, m, s, isPast };
}
每秒刷新
用 setInterval(tick, 1000),但要注意:如果用户切换到其他标签页,浏览器会节流 setInterval,导致时间不准。不过对于倒计时场景,1秒的误差完全可以接受,不需要用 requestAnimationFrame 或 Web Worker 来保证精度。
快捷预设
这里有个细节:周末的判定。如果今天是周六,那"本周末"应该是明天(周日),而不是今天。我用了个小技巧:
javascript
var daysUntilSat = (6 - day + 7) % 7;
if (daysUntilSat === 0) daysUntilSat = 7; // 如果是周六,跳到下周六
踩坑记录
-
datetime-local的兼容性:Safari 15 以下不支持,需要 fallback。不过我做了渐进增强------不支持的浏览器会降级为文本输入框,用户手动输入日期格式。 -
XSS 风险 :事件名称是用户输入,直接插到
innerHTML里会有 XSS 风险。必须用escapeHtml()转义:
javascript
function escapeHtml(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
- 移动端布局 :4 个数字盒子在 320px 宽度下会挤在一起。用
flex-wrap和min-width解决,同时在小屏下减小字号。
对 AI 辅助编程的感受
这次开发过程最大的感受是:AI 是很好的结对编程伙伴,但你不能完全放手。
- AI 擅长:快速搭框架、写重复性代码、查 API 用法
- AI 不擅长:理解业务边界条件、处理浏览器兼容性、做交互细节的取舍
比如 AI 第一次生成的代码完全没有考虑"用户没设置日期就点击开始"的情况,是我在测试时发现的。这种边界情况,需要开发者自己把关。
另外,给 AI 的 prompt 越具体,输出质量越高。比如"进度条需要 clamp 到 0-100"、"到期后切换为正计时并显示红色",比"加个进度条"要好得多。
适用场景
这个工具适合:
- 团队项目截止日期追踪
- 个人目标倒计时(考试、旅行、健身计划)
- 产品上线倒计时(配合全屏模式投屏)
- 日常提醒(比如服务器证书到期)
如果你也有类似需求,可以直接在线使用:倒计时器 - 在线日期倒计时计算工具
代码已经完整放在页面上,纯前端实现,所有计算都在浏览器本地完成,不会上传任何数据。你也可以直接右键查看源码,或者 F12 拷贝下来自己改。