我给自己写了一个本地番茄钟:安静、不催促、UI 全自定义实录
目录
- 缘起:为什么我不想要任何云端番茄钟
- 需求拆解:从"能计时"到"能专注"
- 技术选型:一个 HTML 文件就够了
- 核心实现一:计时与暂停的精度
- 核心实现二:专注记录与统计
- UI 定制:番茄钟也是"自己的工作台"
- 三个让我卡了一阵的坑
- 结尾:专注工具的"不打扰"哲学
1. 缘起:为什么我不想要任何云端番茄钟
我用番茄钟很多年——25 分钟专注、5 分钟休息。听起来简单,但市面上的工具总让我皱眉:
- 云端同步要登录、要账号、要担心哪天收费策略变了;
- 功能塞满:任务管理、习惯打卡、白噪音、社交排行……我只想计时;
- UI 改不动,配色刺眼或动画浮夸,反而打断专注;
- 通知太积极,“该休息了”"该回来了"弹窗比我自己还急。
我忽然意识到:专注工具的本分是"帮我进入心流",而不是"替我管理时间"。既然现成工具都在背离这个初衷,那就自己写一个。
2. 需求拆解:从"能计时"到"能专注"
把"做一个番茄钟"拆成可验收清单:
| 诉求(人话) | 拆解后(可验收) |
|---|---|
| 能计时 | 25 分钟专注 + 5 分钟休息,可自定义时长 |
| 能暂停 | 暂停/继续,不丢精度 |
| 能记录 | 每次完成存本地,按日期聚合 |
| 能统计 | 今日/本周专注次数与总时长,简单曲线 |
| 不打扰 | 计时结束轻提示,不弹窗不响铃不抢焦点 |
| UI 我说了算 | 配色、字体、布局可自定义 |
这张表决定了番茄钟的骨架。功能不多,但每一项都做到位——这是我对所有"自己用的工具"的一贯要求。
3. 技术选型:一个 HTML 文件就够了
我犹豫过要不要上 Electron 或 Tauri,但仔细想需求——我不需要系统托盘、不需要全局快捷键、不需要后台运行,浏览器完全够用:
| 能力 | 方案 |
|---|---|
| 计时 | setInterval + 时间戳差值 |
| 持久化 | localStorage 存配置 + IndexedDB 存记录 |
| 通知 | Notification API(可关) |
| 进度可视化 | SVG 环形进度条 |
整个番茄钟就是一个 HTML 文件,双击就能开。没有安装包、没有更新提示、没有后台进程——配置和记录都在本地,换机器导出 JSON 即可。
心得:选型时先问"我到底需要什么"。本地小工具的最高境界是双击即用、关掉即走。
4. 核心实现一:计时与暂停的精度
计时器最容易踩的坑:用 setInterval 累加秒数。setInterval 不精确,长时间运行会漂移,暂停再继续误差累积。
正确做法是存开始时间戳,每次 tick 用 Date.now() - startTime 算剩余:
let startTime, remaining = 25 * 60 * 1000, running = false;
function tick() {
if (!running) return;
const elapsed = Date.now() - startTime;
const left = remaining - elapsed;
updateDisplay(left);
if (left <= 0) finishPhase();
}
function pause() {
remaining -= Date.now() - startTime; // 冻结剩余
running = false;
}
function resume() {
startTime = Date.now(); // 重置起点
running = true;
tick();
}
这样无论 setInterval 多不准、暂停多少次,剩余时间永远基于真实时间戳,零漂移。
心得:计时类功能的正确性,不靠"定时器多准",而靠"用绝对时间做基准"。这是所有计时工具的通用心法。
5. 核心实现二:专注记录与统计
每次完成一个番茄,往 IndexedDB 写一条记录:
await db.add('sessions', {
startedAt: Date.now() - 25 * 60 * 1000,
duration: 25 * 60,
type: 'focus', // focus / break
label: '写作', // 可选标签
});
统计就是按日期聚合:
const today = await db.getAllFromIndex('sessions', 'by-date', todayKey);
const totalMin = today.filter(s => s.type === 'focus')
.reduce((sum, s) => sum + s.duration, 0) / 60;
几个让统计"有用"的细节:
- 只统计完成的番茄。被打断的不计入,避免"虚假成就感";
- 按标签聚合。写作/编码/阅读分开看,知道时间花在哪;
- 本周曲线而非数字仪表盘。趋势比绝对值更有意义——今天比昨天少不必焦虑,看周走势即可。
心得:统计的价值不在"数字漂亮",而在"能反映真实的工作节奏"。任何会制造焦虑的指标(连续天数、未完成数)都该砍掉。
6. UI 定制:番茄钟也是"自己的工作台"
本地工具最大的特权,是 UI 完全我说了算。我给自己定了几个商业软件不会这么做的设计选择:
- 中央一个大环。环形进度条 + 剩余时间,占视觉中心,其余全留白;
- 配色随阶段切换。专注时暖橙、休息时青绿,一眼知道当前在哪个阶段;
- 无数字焦虑。不显示"今日已完成 X 个"在主界面——统计在另一个 tab,专注时不打扰;
- 结束时轻提示。一个温和的桌面通知,不响铃不抢焦点,关掉就继续。
这些设计在商业产品里几乎不可能出现——它们太"个人化"了。但本地工具的意义,恰恰就是让软件贴合你的专注习惯,而不是让你迁就软件的功能预设。
7. 三个让我卡了一阵的坑
坑一:页面切到后台计时停了。 浏览器会把后台 tab 的 setInterval 降频到 1Hz 甚至更低,UI 看起来"卡住"。但因为我是用 Date.now() - startTime 算剩余,切回前台瞬间自动校正,不会丢时间。这正好印证了第 4 节那个心法的价值。
坑二:Notification 权限要用户手势触发。 第一次调用 Notification.requestPermission() 必须在用户点击事件里,不能页面加载就弹权限框。解决办法是首次启动引导用户点一下"启用通知"按钮。
坑三:IndexedDB 索引不能改。 一开始没建 by-date 索引,后来想按日期查询要重建整个库。教训:第一版就把"将来一定会用的查询字段"建好索引,否则数据多了迁移很痛。
8. 结尾:专注工具的"不打扰"哲学
做完这个番茄钟,我对"专注工具"有了一种更深的体会:
- 它不抢你注意力。没有任务列表、没有排行榜、没有"今日目标"弹窗;
- 它尊重你的节奏。时长可调、可暂停、可跳过,不强制 25 分钟;
- 它尊重你的数据。记录在本地,导出 JSON 即可迁移。
在这个所有软件都在抢用户时长的时代,一个"帮你专注、自己却不出声"的本地工具,反而成了一种奢侈。
如果你也想要一个安静的番茄钟,我强烈建议自己写一个。技术门槛比想象中低,一个 HTML 文件就够;真正难的是克制住加功能的冲动——不做任务管理、不做社交排行、不做"今日总结",就做一个老老实实的计时器。
工具小而静,价值往往不在多,而在"它永远在那里,永远不出声"。
- 点赞
- 收藏
- 关注作者
评论(0)