我给自己写了一个本地番茄钟:安静、不催促、UI 全自定义实录

举报
yd_294377644 发表于 2026/10/25 22:57:42 2026/10/25
【摘要】 市面番茄钟工具要么塞满功能、要么强推云端同步、要么 UI 改不动。我想要一个安静、本地、能配合自己工作节奏的专注工具,于是自己写了一个。这篇博客记录从需求到落地的全过程:技术选型、计时实现、专注统计、UI 定制心得,以及那些自己写一遍才明白的细节。

目录

  1. 缘起:为什么我不想要任何云端番茄钟
  2. 需求拆解:从"能计时"到"能专注"
  3. 技术选型:一个 HTML 文件就够了
  4. 核心实现一:计时与暂停的精度
  5. 核心实现二:专注记录与统计
  6. UI 定制:番茄钟也是"自己的工作台"
  7. 三个让我卡了一阵的坑
  8. 结尾:专注工具的"不打扰"哲学

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. 结尾:专注工具的"不打扰"哲学

做完这个番茄钟,我对"专注工具"有了一种更深的体会:

  1. 它不抢你注意力。没有任务列表、没有排行榜、没有"今日目标"弹窗;
  2. 它尊重你的节奏。时长可调、可暂停、可跳过,不强制 25 分钟;
  3. 它尊重你的数据。记录在本地,导出 JSON 即可迁移。

在这个所有软件都在抢用户时长的时代,一个"帮你专注、自己却不出声"的本地工具,反而成了一种奢侈。

如果你也想要一个安静的番茄钟,我强烈建议自己写一个。技术门槛比想象中低,一个 HTML 文件就够;真正难的是克制住加功能的冲动——不做任务管理、不做社交排行、不做"今日总结",就做一个老老实实的计时器。

工具小而静,价值往往不在多,而在"它永远在那里,永远不出声"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。