优雅架构:基于纯原生 JS 与 Web API 构建零依赖高效番茄钟
优雅架构:基于纯原生 JS 与 Web API 构建零依赖高效番茄钟
在现代前端开发中,轻量化工具往往容易被过度工程化。为了实现一个计时器,很多项目引入庞大的第三方框架或沉重的音频、图表依赖。
本文基于开源作品 「番茄专注钟」,深入拆解如何在零外部依赖、纯原生 HTML5 / CSS3 / JavaScript 的前提下,利用现代浏览器内置的 Web API,实现一个高精度、高质感、具备音频合成与数据持久化的沉浸式专注工具。
一、 系统架构与状态机设计
番茄工作法的本质是一个由时间驱动的有限状态机(Finite State Machine, FSM)。
stateDiagram-v2
[*] --> 专注_Work: 初始加载/重置 (25m)
专注_Work --> 短休_ShortBreak: 计时结束 (未满4个番茄, 5m)
专注_Work --> 长休_LongBreak: 计时结束 (满4个番茄, 15m)
短休_ShortBreak --> 专注_Work: 休息结束 (25m)
长休_LongBreak --> 专注_Work: 休息结束 (25m)
state 专注_Work {
[*] --> 倒计时中
倒计时中 --> 暂停: 点击暂停
暂停 --> 倒计时中: 点击继续
}
1. 状态配置字典化
我们将各阶段的业务属性(时长、显示名称、主题色系、发光光晕)统一声明在配置表中,保证业务逻辑与 UI 渲染解耦:
const MODES = {
work: {
name: '专注',
duration: 25 * 60,
accent: '#ff6b4a',
glow: 'rgba(255, 107, 74, 0.25)'
},
shortBreak: {
name: '短休',
duration: 5 * 60,
accent: '#20c997',
glow: 'rgba(32, 201, 151, 0.25)'
},
longBreak: {
name: '长休',
duration: 15 * 60,
accent: '#4dabf7',
glow: 'rgba(77, 171, 247, 0.25)'
}
};
二、 核心技术实现解析
1. SVG 动态环形进度条的数学计算
传统 CSS 进度条多为线性,而环形表盘能给用户更自然的“时间流逝”心智模型。我们使用 SVG <circle> 的 stroke-dasharray 与 stroke-dashoffset 属性进行精确控制。
原理公式
对于半径为 的圆:
- 初始满格状态:
stroke-dashoffset = 0。 - 随着时间流逝,剩余比例为 ,偏移量动态计算为:
const CIRCUMFERENCE = 2 * Math.PI * 100;
function renderTime() {
const total = MODES[currentMode].duration;
const progress = (total - timeLeft) / total;
const offset = CIRCUMFERENCE * (1 - progress);
// 线性动画平滑过渡
document.getElementById('dialProgress').style.strokeDashoffset = offset;
}
配合 SVG 容器的逆时针 90° 旋转(transform: rotate(-90deg)),进度条从正上方 12 点钟方向顺畅起步。
2. 纯代码级音效:Web Audio API 实时合成
为了避免引入外部 MP3/WAV 资产带来的网络请求与加载失败风险,我们利用浏览器原生的 AudioContext 实时合成优雅的双音正弦波提示音。
function playChime() {
try {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioCtx();
const osc = ctx.createOscillator(); // 音频振荡器
const gain = ctx.createGain(); // 音量包络增益
osc.type = 'sine'; // 纯净正弦波
// 频段滑音:从 D5 (587.33Hz) 平滑过渡到 A5 (880Hz)
osc.frequency.setValueAtTime(587.33, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(880, ctx.currentTime + 0.3);
// 音量淡入淡出(避免爆音)
gain.gain.setValueAtTime(0.15, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.8);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start();
osc.stop(ctx.currentTime + 0.8);
} catch (e) {
console.warn('AudioContext not supported or permission denied', e);
}
}
方案优势:
- 0 字节资产开销:无需外部静态资源。
- 超低延迟:毫秒级调用,无网络阻塞。
- 声音柔和:平滑指数衰减包络,适合专注工作场景。
3. 数据层:自然日隔离与持久化设计
轻量工具的核心体验在于数据留存与隐私安全。我们设计了以“自然日(YYYY-MM-DD)”为主键的隔离策略:
function ensureTodayRecords() {
const data = loadRecords();
const today = getTodayKey();
// 跨越午夜打开页面时,自动将统计计数归零并开启新一天的日志
if (data.date !== today) {
data.date = today;
data.count = 0;
data.minutes = 0;
data.list = [];
saveRecords(data);
}
return data;
}
每完成一个番茄钟(25分钟),系统自动沉淀当前选择的分类标签(工作/学习/编程/阅读)及完成时间戳,形成本地可视化时间审计流。
4. 视觉层:CSS 自定义属性实现动态氛围联动
在“专注模式”(橙红)与“休息模式”(薄荷绿 / 晴空蓝)之间切换时,界面的主色调、环形光晕与按钮悬浮阴影实现全局联动。
利用 CSS 变量注入,JS 仅需操作根节点的两个变量:
:root {
--accent-work: #ff6b4a;
--accent-work-glow: rgba(255, 107, 74, 0.25);
--current-accent: var(--accent-work);
--current-glow: var(--accent-work-glow);
}
.dial-progress {
stroke: var(--current-accent);
filter: drop-shadow(0 0 8px var(--current-glow));
}
function updateTheme() {
const root = document.documentElement;
const modeObj = MODES[currentMode];
root.style.setProperty('--current-accent', modeObj.accent);
root.style.setProperty('--current-glow', modeObj.glow);
}
三、 工程化思考与性能总结
- 零运行时依赖:整套应用体积仅约 10KB,首屏秒开。
- 渲染优化:倒计时每秒触发时仅局部更新文本节点与 SVG
stroke-dashoffset,避免触发全局 DOM 重排(Reflow)。 - 多标签页感知:页面 Title 动态同步剩余时间,切后台时随时掌握专注进度。
- 点赞
- 收藏
- 关注作者
评论(0)