萌萌专注钟:用 React + Motion 打造一个治愈系番茄钟
教程#React#TypeScript

萌萌专注钟:用 React + Motion 打造一个治愈系番茄钟

>
~5 min read

#萌萌专注钟:用 React + Motion 打造一个治愈系番茄钟

先说结论:专注不一定要苦大仇深。

市面上的番茄钟应用,要么极简到只剩数字,要么功能臃肿像企业软件。我想做一个不一样的——一个让你看一眼就想开始学习的番茄钟。它有猫耳朵、会眨眼、会脸红,专注完成的时候还会撒彩带。

这就是「萌萌专注钟」。

在线体验:AI Studio 应用

##它能做什么

###三种模式

模式默认时长配色用途
专注25 分钟珊瑚粉 #F4A896click to copy深度工作
短休5 分钟薄荷绿 #B8E0D2click to copy放松眼睛
长休15 分钟薰衣草紫 #E2D1F9click to copy彻底休息

###核心功能

  • 可视化计时器:SVG 圆形进度条 + 猫脸表情动画
  • 任务管理:添加、完成、删除,实时统计完成度
  • 深色模式:一键切换,所有颜色自适应
  • 完成彩带:专注结束时 canvas-confetticlick to copy 撒花
  • 每日名言:随机展示一句激励话语
  • 自定义时长:三种模式的时间都可以调节

##技术栈

React 19 + TypeScript 5.8 Tailwind CSS 4.1 + Motion 12(动画) Lucide React(图标)+ Canvas Confetti(彩带) Vite 6(构建)+ Express 4(服务端) click to copy

整个应用只有一个 App.tsxclick to copy,约 700 行,没有路由、没有状态管理库、没有后端 API。全部状态用 useStateclick to copy 管理,动画交给 Motion。

这是有意为之的——一个专注工具不应该让自己变得复杂。

##设计:治愈系的视觉系统

###配色方案

四种颜色撑起整个界面:

珊瑚色 #F4A896 → 专注模式主色,温暖有活力 薄荷色 #B8E0D2 → 短休模式主色,清爽放松 薰衣草 #E2D1F9 → 长休模式主色,柔和梦幻 金色 #D4A574 → 装饰色,点缀用 click to copy

每种模式切换时,所有 UI 元素——进度条、按钮、卡片、甚至背景中漂浮的装饰圆点——都会跟着变色。这不是简单的换色,而是整个氛围的转变。

###猫脸计时器

这是整个应用最核心的视觉元素。一个 320px 的圆形,里面藏着:

  1. 猫耳朵:两个倾斜的白色圆角三角,顶部露在圆外
  2. 眼睛:两个小黑点,专注时会有节奏地眨眼(scaleYclick to copy 在 1 和 0.1 之间循环)
  3. 腮红:眼睛下方的半透明椭圆,颜色跟随当前模式
  4. 嘴巴:底部的小弧线,专注时会微微张合
  5. 进度环:SVG 圆环,用 strokeDashoffsetclick to copy 动画显示剩余时间

外围还有缓慢旋转的虚线装饰环和上下漂浮的彩色圆点,营造梦幻感。

###玻璃态卡片

任务卡片使用毛玻璃效果:

>hljs css.0 lines
/* 亮色模式 */
.glass-card {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
}

半透明白底 + 背景模糊 + 柔和阴影,让卡片看起来漂浮在渐变背景之上。

###字体选择

>hljs css.0 lines
--font-heading: 'Playfair Display';  /* 标题,优雅衬线 */
--font-body: 'LXGW WenKai';          /* 正文,霞鹜文楷 */
--font-mono: 'JetBrains Mono';        /* 时间数字,等宽 */

霞鹜文楷是关键。它给整个应用定下了「手写感、亲切感」的基调,和猫脸的设计语言完美契合。

##技术实现:关键细节

###1. 计时器逻辑

useEffectclick to copy + setIntervalclick to copy 实现,每一秒 timeLeftclick to copy 减 1。归零时触发完成处理:

>hljs typescript.0 lines
useEffect(() => {
  if (isActive && timeLeft > 0) {
    timerRef.current = setInterval(() => {
      setTimeLeft((prev) => prev - 1);
    }, 1000);
  } else if (timeLeft === 0) {
    handleTimerComplete();
  }
  return () => {
    if (timerRef.current) clearInterval(timerRef.current);
  };
}, [isActive, timeLeft]);

handleTimerCompleteclick to copy 做三件事:暂停计时器、播放提示音、撒彩带。

###2. SVG 圆形进度条

这是纯数学 + CSS 动画的组合:

>hljs typescript.0 lines
const circumference = 2 * Math.PI * 140; // 半径 140 的圆周长
const progress = ((totalTime - timeLeft) / totalTime) * circumference;

// strokeDasharray = 圆周长,strokeDashoffset = 圆周长 - 已走过长度
<motion.circle
  strokeDasharray={circumference}
  animate={{ strokeDashoffset: circumference - progress }}
  transition={{ duration: 0.5, ease: 'easeOut' }}
/>

用 Motion 的 animateclick to copy 属性驱动 strokeDashoffsetclick to copy 的变化,每次更新都有 0.5 秒的缓动过渡,进度条不会突然跳变。

###3. 模式切换动画

模式切换按钮使用 Motion 的 layoutIdclick to copy 实现共享布局动画:

>hljs typescript.0 lines
{mode === m && (
  <motion.div
    className="absolute inset-0 rounded-xl bg-gradient-to-br gradient opacity-10"
    layoutId="activeMode"
    transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }}
  />
)}

当你从「专注」切到「短休」,高亮背景会以弹性动画滑过去,而不是突然消失再出现。

###4. 任务列表的增删动画

任务项使用 AnimatePresenceclick to copy + layoutclick to copy 属性:

>hljs typescript.0 lines
<AnimatePresence mode="popLayout">
  {tasks.map((task) => (
    <motion.div
      key={task.id}
      layout           // 自动处理列表重排动画
      initial={{ opacity: 0, x: -20, height: 0 }}
      animate={{ opacity: 1, x: 0, height: 'auto' }}
      exit={{ opacity: 0, scale: 0.95, height: 0 }}
    >

新增任务从左滑入,删除时缩小消失,列表重排时自动平滑过渡。

###5. 彩带庆祝

专注完成时的彩带效果:

>hljs typescript.0 lines
if (mode === 'focus') {
  confetti({
    particleCount: 100,
    spread: 70,
    origin: { y: 0.6 },
    colors: ['#FDA4AF', '#F0ABFC', '#67E8F9', '#F4A896', '#B8E0D2']
  });
}

注意颜色数组,用的是和主题一致的五种色彩,而不是默认的红黄蓝。细节决定质感。

###6. 深色模式

所有颜色通过 isDarkModeclick to copy 状态三元表达式切换:

>hljs typescript.0 lines
isDarkMode
  ? 'bg-[#2A2A2A] hover:bg-[#3A3A3A] text-[#B8ADA3]'
  : 'bg-white/60 hover:bg-white text-[#8B7E74]'

深色模式没有简单地反转颜色,而是用了一套独立的暗色调色板。背景变为暖灰,文字变为暖白,保持「治愈感」不变。

##动画编排

整个页面的入场动画是精心编排的:

0.0s Header 标题从左滑入 0.1s Header 按钮从右滑入 0.2s 时钟从中心缩放淡入 0.3s 模式切换器淡入 0.3s 右侧任务卡片从右滑入 0.4s 控制按钮从下滑入 0.5s 名言卡片淡入 0.8s 页脚淡入 click to copy

每个元素都有延迟,形成从中心向外扩散的入场节奏。

##响应式布局

使用 12 列 Grid 系统:

>hljs typescript.0 lines
<main className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
  {/* 左侧计时器:7列 */}
  <div className="lg:col-span-7 flex flex-col items-center">
    {/* 时钟 */}
  </div>

  {/* 右侧任务+名言:5列 */}
  <div className="lg:col-span-5 space-y-6">
    {/* 任务卡片 */}
    {/* 名言卡片 */}
  </div>
</main>

桌面端左右分栏,移动端上下堆叠。时钟尺寸也做了响应式调整:w-72 h-72 md:w-80 md:h-80click to copy

##项目结构

src/ ├── main.tsx # 入口,挂载 React ├── App.tsx # 全部逻辑和组件(~700行) └── index.css # 全局样式、自定义动画关键帧 click to copy

是的,只有一个组件文件。对于这种规模的应用,拆分反而增加认知负担。所有常量(MODE_CONFIGclick to copyQUOTESclick to copy)都定义在文件顶部,类型定义紧跟其后,组件嵌套在主函数内部。

##写在最后

这个项目的核心设计理念很简单:让专注成为一件愉快的事

它不是一个追求功能大而全的生产力工具。它只想做一件事——在你决定专注的那一刻,给你一个温暖的、有生命力的陪伴。

猫耳朵会动,腮红会变色,专注结束会撒花。这些「无用」的细节,恰恰是让一个工具从「能用」变成「喜欢用」的关键。

技术是实现手段,不是目的。选择 React + Motion 是因为它们能让我用最少的代码实现最流畅的动画。选择单文件架构是因为对于这个规模的应用,简单就是最好的架构。

如果你也想做一个让自己开心的 Side Project,我的建议是:别想太多,先让它跑起来,然后把它做得好看。

// EOF

项目基于 Apache 2.0 协议开源,使用 React 19 + TypeScript + Tailwind CSS 4 + Motion 构建。

> tags
#React#TypeScript#番茄钟#Tailwind CSS#Motion#前端
> related_posts
>cd /blog_