现代番茄时钟

举报
yd_292659020 发表于 2026/10/14 01:59:19 2026/10/14
【摘要】 从零到一:用原生 Web 技术构建现代番茄时钟一款面向工作人群的专注计时工具,从需求分析到上架华为云开发者作品展览馆的完整开发纪实。 前言番茄工作法(Pomodoro Technique)由 Francesco Cirillo 在 1987 年提出,核心简单:25 分钟专注 + 5 分钟休息,循环往复。这个方法至今仍是无数知识工作者对抗拖延、保持专注的利器。本文记录了「番茄时钟 · Pom...

从零到一:用原生 Web 技术构建现代番茄时钟

一款面向工作人群的专注计时工具,从需求分析到上架华为云开发者作品展览馆的完整开发纪实。

前言

番茄工作法(Pomodoro Technique)由 Francesco Cirillo 在 1987 年提出,核心简单:25 分钟专注 + 5 分钟休息,循环往复。这个方法至今仍是无数知识工作者对抗拖延、保持专注的利器。

本文记录了「番茄时钟 · Pomodoro Timer」应用从需求定义、架构设计、编码实现到最终发布上架的全过程。没有花哨的框架,只有 HTML + CSS + JavaScript 三件套——有时候,最朴素的技术栈反而能交付最纯粹的产品体验。


一、需求分析

目标用户

工作人群——程序员、设计师、产品经理、文案写作者……他们的共同特征是:

  • 需要长时间保持专注,但容易被消息、邮件打断
  • 对工具的 UI 品质有要求,拒绝粗糙的界面
  • 希望工具轻量、即开即用,不想安装额外软件

核心功能矩阵

功能 优先级 说明
25 分钟专注倒计时 P0 番茄工作法的基础单元
5 分钟短休息 / 15 分钟长休息 P0 每 4 个番茄后自动进入长休息
开始 / 暂停 / 重置 / 跳过 P0 完整的计时控制
圆形进度环 P0 视觉化倒计时,一眼可知剩余时间
任务标签 P1 标注当前在做什么,便于回顾
会话统计 P1 今日完成数、专注总时长、当前轮次
历史记录 P1 记录每次完成的番茄钟
声音 + 浏览器通知 P1 番茄完成时提醒切换
深色模式 P1 夜间工作者必备
自定义时长 P2 不同人有不同的专注节奏
键盘快捷键 P2 Power user 的效率需求

设计风格

现代极简——这是定调。具体拆解为:

  • 卡片式布局,柔和阴影,圆角设计
  • 颜色语义化:工作用番茄红、短休息用青绿、长休息用蓝色
  • 流畅的 CSS 过渡动画,不突兀也不拖沓
  • 响应式设计,桌面和移动端皆可用

二、技术选型

为什么不用框架?

这是一个单页应用,交互逻辑集中在计时器状态机上。引入 React/Vue 意味着:

  • 构建工具链(Vite/Webpack)的配置成本
  • 框架运行时的体积开销
  • 对一个 3 文件的项目来说,框架的收益趋近于零

原生 JS 的 IIFE 模式 + DOM 直接操作,代码量约 400 行,完全可控。

最终技术栈

HTML          — 语义化结构
CSS           — CSS 变量 + 媒体查询,主题切换零 JS 重绘
JavaScript    — IIFE 封装,状态机模式
Node.js       — 仅用于静态文件服务器(40 行)

文件结构

pomodoro-timer/
├── index.html      # 页面结构(157 行)
├── style.css       # 现代样式 + 深色模式(573 行)
├── app.js          # 核心逻辑 + 状态机(427 行)
├── server.js       # 静态文件服务器(40 行)
├── package.json    # 项目配置
└── README.md       # 项目文档

三、架构设计

状态机模型

整个应用的核心是一个三态有限状态机:

         ┌──────────────────────────────┐
         │                              │
    ┌────▼────┐    完成    ┌──────────┐  │
    │  WORK   │──────────▶│ SHORT    │  │
    │ (25min) │           │ BREAK    │  │
    └─────────┘           │ (5min)   │  │
         ▲                └──────────┘  │
         │                     │        │
         │  完成               │ 完成    │
         │                     ▼        │
         │                ┌──────────┐  │
         │                │ LONG     │  │
         │  完成          │ BREAK    │  │
         └────────────────│ (15min)  │  │
                          └──────────┘  │
                         (每 4 个番茄触发)│

状态对象

const state = {
    mode: 'work',           // 'work' | 'shortBreak' | 'longBreak'
    isRunning: false,       // 是否正在倒计时
    timeLeft: 25 * 60,      // 剩余秒数
    totalTime: 25 * 60,     // 当前会话总秒数
    pomodoroCount: 0,       // 今日完成番茄数
    focusMinutes: 0,        // 今日专注总分钟数
    cyclePosition: 0,       // 当前轮次位置 (0~N)
    history: [],            // 历史记录数组
    settings: { ... },      // 用户自定义设置
    intervalId: null,       // setInterval 句柄
};

设计要点:

  • 单一状态源:所有 UI 更新都从 state 派生,避免多源同步问题
  • 模式切换即状态重置:setMode() 同时重置 timeLeft 和 totalTime,保证一致性
  • 轮次追踪:cyclePosition 从 0 递增到 longBreakInterval,达到阈值后归零并进入长休息

四、核心实现

4.1 圆形进度环

用 SVG 的 stroke-dasharray + stroke-dashoffset 实现进度动画:

const RING_RADIUS = 130;
const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS; // ~816.4

function updateDisplay() {
    const progress = state.totalTime > 0 
        ? state.timeLeft / state.totalTime : 0;
    const offset = RING_CIRCUMFERENCE * (1 - progress);
    el.ringProgress.style.strokeDashoffset = offset;
}

原理:stroke-dasharray 设为圆周长(816.4),stroke-dashoffset 从 0(满圆)到 816.4(空圆)线性变化。CSS transition: stroke-dashoffset 1s linear 让每秒的进度变化丝滑过渡。

SVG 整体旋转 -90deg,让进度从顶部 12 点钟方向开始,符合直觉。

4.2 模式切换与颜色系统

三种模式各有专属配色,通过 CSS 变量动态切换:

:root {
    --work-color: #e8553e;      /* 番茄红 */
    --short-color: #2db89a;     /* 青绿 */
    --long-color: #5b8def;      /* 海蓝 */
    --accent: var(--work-color); /* 当前模式色 */
}
function updateModeColors() {
    const config = MODE_CONFIG[state.mode];
    document.documentElement.style
        .setProperty('--accent', `var(${config.color})`);
}

切换模式时,所有引用 --accent 的元素(按钮、进度环、徽章、统计数字)同步变色,零 JS 重绘,纯 CSS 过渡。

4.3 深色模式

通过 data-theme="dark" 属性 + CSS 变量覆盖实现:

[data-theme="dark"] {
    --bg: #1a1a2e;
    --surface: #232342;
    --text: #e8e8f0;
    /* ... */
}

主题偏好存储在 localStorage,刷新页面后自动恢复。切换按钮的太阳/月亮图标通过 CSS display 切换,无需 JS 判断。

4.4 声音提醒

不依赖任何音频文件,用 Web Audio API 实时合成双音提示:

function playSound() {
    const audioCtx = new AudioContext();
    [880, 1320].forEach((freq, i) => {
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.connect(gain);
        gain.connect(audioCtx.destination);
        osc.frequency.value = freq;
        osc.type = 'sine';
        // ADSR 包络
        gain.gain.setValueAtTime(0, now + i * 0.15);
        gain.gain.linearRampToValueAtTime(0.3, now + i * 0.15 + 0.02);
        gain.gain.exponentialRampToValueAtTime(0.001, now + i * 0.15 + 0.4);
        osc.start(now + i * 0.15);
        osc.stop(now + i * 0.15 + 0.4);
    });
}

880Hz → 1320Hz 的上行二音,听感温和不刺耳,适合频繁触发的工作场景。

4.5 键盘快捷键

document.addEventListener('keydown', (e) => {
    if (e.target.tagName === 'INPUT') return; // 输入框中不拦截
    switch (e.code) {
        case 'Space': toggleStartPause(); break;
        case 'KeyR':  resetTimer();      break;
        case 'KeyS':  skipSession();     break;
        case 'Escape': closeSettings();  break;
    }
});

空格键开始/暂停是最常用的操作,单键直达,无需鼠标移动。


五、UI 设计细节

视觉层级

┌─────────────────────────────┐
│  🍅 番茄时钟          [☀️]  │  ← Header(品牌 + 主题切换)
├─────────────────────────────┤
│  [专注工作] [短休息] [长休息] │  ← 模式切换 Tab
├─────────────────────────────┤
│        ┌───────────┐        │
│        │  25:00    │        │  ← 圆形进度环 + 时间显示
│        │ 点击开始   │        │
│        └───────────┘        │
│   [你正在做什么?]           │  ← 任务标签输入
│   [↺]  [开始]  [⏭]         │  ← 控制按钮
├─────────────────────────────┤
│  0       0分      0/4       │  ← 统计面板
│  今日番茄  专注时长  轮次    │
├─────────────────────────────┤
│  今日记录                    │  ← 历史列表
│  🍅 写博客  14:30           │
└─────────────────────────────┘

设计决策记录

决策 选择 理由
字体 Inter 现代无衬线,数字字形优秀,tabular-nums 等宽显示
计时数字 56px / 700 weight 视觉焦点,一眼可读
进度环 280px / 6px stroke 不会喧宾夺主,但足够醒目
主按钮 140px min-width 拇指可触达,移动端友好
圆角 16px 卡片 / 10px 内部 柔和但不失结构感
阴影 0 8px 40px rgba(0,0,0,0.1) 轻浮于背景,但不突兀

六、服务器实现

极简静态文件服务器,40 行代码:

const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
    let urlPath = req.url === '/' ? '/index.html' : req.url;
    const filePath = path.join(__dirname, path.basename(urlPath));

    fs.readFile(filePath, (err, data) => {
        if (err) {
            res.writeHead(404, { 'Content-Type': 'text/plain' });
            res.end('404 Not Found');
            return;
        }
        const ext = path.extname(filePath);
        const mime = MIME_TYPES[ext] || 'application/octet-stream';
        res.writeHead(200, { 'Content-Type': mime });
        res.end(data);
    });
});

server.listen(8080, '0.0.0.0');

关键细节:0.0.0.0 而非 127.0.0.1——在华为云 CloudIDE 沙箱中,服务必须监听所有网卡才能被端口转发机制访问。



开发反思

做对了的事

1. 技术栈克制

没有引入任何框架、构建工具、依赖包。npm install 零依赖,node server.js 即跑。对于这个体量的应用,这是正确的选择——复杂度与收益成正比。

2. CSS 变量驱动主题

深色模式和模式配色都通过 CSS 变量实现,JS 只需修改一个变量值,所有相关元素自动过渡。这比 JS 逐个操作 DOM style 的方案优雅得多。

3. 状态集中管理

单一 state 对象 + 纯函数式 UI 更新,调试时只需检查状态变化点,不需要追踪多个分散的变量。

可以改进的地方

1. 数据持久化

当前设置和历史记录存在 localStorage,换设备即丢失。如果接入云端同步(如华为云 OBS),体验会更完整。

2. 单元测试

计时逻辑(特别是模式切换、轮次追踪)适合用单元测试覆盖。当前纯手测,边界 case(如自定义时长为 1 分钟、长休息间隔为 2)未系统验证。

3. PWA 支持

添加 Service Worker + manifest 可以让应用离线可用、可安装到桌面。对于「即开即用」的工具类应用,这是自然的下一步。


结语

这个项目证明了一件事:不是所有应用都需要框架。

当你清楚自己的需求边界——一个单页、一个状态机、三种模式——原生 Web 技术的三件套足以交付一个设计精良、体验流畅的产品。克制的技术选型反而让开发更快、代码更可读、维护更简单。

番茄时钟现在每天陪我工作。如果你也是那个在消息洪流中试图保持专注的人,不妨试试。

项目地址:GitCode
在线预览:展览馆


2026-10-14 · 华为云开发者作品展览馆

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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