프로젝트

뽀모도로 타이머 앱 제작 과정

시간 관리는 교사에게도, 개발 공부를 하는 저에게도 늘 숙제 같은 존재입니다. 수업 준비, 업무 처리, 코딩 공부를 병행하다 보면 하루가 금방 지나가버리거든요. 그래서 뽀모도로 기법을 실천하기 위한 타이머 앱을 직접 만들어보기로 했습니다. 이 글은 기획부터 완성까지의 과정을 프로젝트 일지 형태로 기록한 것입니다.

뽀모도로 기법이란?

뽀모도로 기법은 1980년대 Francesco Cirillo가 고안한 시간 관리 방법론입니다. 핵심 규칙은 간단합니다:

  1. 25분간 집중해서 작업한다 (1 뽀모도로)
  2. 5분간 짧은 휴식을 취한다
  3. 4번의 뽀모도로를 완료하면 15~30분간 긴 휴식을 취한다

뽀모도로 기법의 매력은 "25분만 집중하면 된다"는 심리적 부담 경감에 있습니다. 끝이 없는 작업도 25분 단위로 쪼개면 시작하기가 훨씬 쉬워집니다.

기획 단계: 필요한 기능 정리

앱을 만들기 전에 어떤 기능이 필요한지 먼저 정리했습니다. 핵심 기능과 추가 기능으로 나누어 우선순위를 세웠습니다.

핵심 기능 (MVP)

  • 25분 작업 타이머
  • 5분 짧은 휴식 타이머
  • 15분 긴 휴식 타이머
  • 시작, 일시정지, 리셋 버튼
  • 타이머 종료 시 알림음

추가 기능 (향후 구현)

  • 완료한 뽀모도로 횟수 표시
  • 사용자 지정 시간 설정
  • 오늘의 집중 시간 통계
노트에 그린 타이머 앱의 초기 와이어프레임

HTML 구조 설계

심플한 UI를 목표로 HTML 구조를 작성했습니다. 원형 타이머 디스플레이를 중앙에 배치하고, 그 아래에 모드 선택 버튼과 제어 버튼을 두는 레이아웃입니다.

<div class="pomodoro-app">
  <h1 class="app-title">Pomodoro Timer</h1>

  <!-- 모드 선택 -->
  <div class="mode-buttons">
    <button class="mode-btn active" data-mode="work">작업</button>
    <button class="mode-btn" data-mode="short-break">짧은 휴식</button>
    <button class="mode-btn" data-mode="long-break">긴 휴식</button>
  </div>

  <!-- 타이머 디스플레이 -->
  <div class="timer-display">
    <span id="minutes">25</span>
    <span class="colon">:</span>
    <span id="seconds">00</span>
  </div>

  <!-- 제어 버튼 -->
  <div class="control-buttons">
    <button id="start-btn">시작</button>
    <button id="pause-btn" disabled>일시정지</button>
    <button id="reset-btn">리셋</button>
  </div>

  <!-- 뽀모도로 카운터 -->
  <div class="pomo-counter">
    완료: <span id="pomo-count">0</span> / 4
  </div>
</div>

CSS 스타일링: 집중을 돕는 디자인

뽀모도로 타이머는 집중을 돕는 도구이므로 디자인도 미니멀하게 가져갔습니다. 작업 모드일 때는 따뜻한 빨간 계열, 휴식 모드일 때는 차분한 녹색 계열로 배경색을 바꾸어 현재 상태를 직관적으로 알 수 있게 했습니다.

.pomodoro-app {
  max-width: 400px;
  margin: 0 auto;
  text-align: center;
  padding: 2rem;
  border-radius: 20px;
  transition: background-color 0.5s ease;
}

.pomodoro-app[data-mode="work"] {
  background-color: #fee2e2;
}

.pomodoro-app[data-mode="short-break"] {
  background-color: #dcfce7;
}

.pomodoro-app[data-mode="long-break"] {
  background-color: #dbeafe;
}

.timer-display {
  font-size: 5rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.05em;
  margin: 2rem 0;
}

font-variant-numeric: tabular-nums는 숫자마다 동일한 너비를 유지하게 해주는 속성입니다. 이것을 적용하지 않으면 타이머 숫자가 바뀔 때마다 너비가 미세하게 달라져서 화면이 떨리는 것처럼 보일 수 있습니다.

JavaScript 타이머 로직

타이머의 핵심 로직은 setInterval을 사용하여 1초마다 남은 시간을 줄이는 것입니다. 처음에는 단순하다고 생각했는데, 실제로 구현해보니 여러 가지 고려할 점이 있었습니다.

const TIMES = {
  work: 25 * 60,
  'short-break': 5 * 60,
  'long-break': 15 * 60
};

let currentMode = 'work';
let timeLeft = TIMES[currentMode];
let timerId = null;
let isRunning = false;
let pomoCount = 0;

function startTimer() {
  if (isRunning) return;
  isRunning = true;

  timerId = setInterval(() => {
    timeLeft--;
    updateDisplay();

    if (timeLeft <= 0) {
      clearInterval(timerId);
      isRunning = false;
      playAlarm();
      handleTimerComplete();
    }
  }, 1000);
}

function updateDisplay() {
  const minutes = Math.floor(timeLeft / 60);
  const seconds = timeLeft % 60;
  document.getElementById('minutes').textContent =
    String(minutes).padStart(2, '0');
  document.getElementById('seconds').textContent =
    String(seconds).padStart(2, '0');
}

겪었던 문제: 타이머 정확도

setInterval은 정확히 1초 간격을 보장하지 않습니다. 브라우저 탭이 비활성화되면 간격이 늘어나기도 합니다. 이 문제를 해결하기 위해 시작 시각을 기록해두고, 현재 시각과의 차이로 남은 시간을 계산하는 방식으로 개선했습니다.

function startTimer() {
  if (isRunning) return;
  isRunning = true;
  const startTime = Date.now();
  const initialTimeLeft = timeLeft;

  timerId = setInterval(() => {
    const elapsed = Math.floor((Date.now() - startTime) / 1000);
    timeLeft = initialTimeLeft - elapsed;

    if (timeLeft <= 0) {
      timeLeft = 0;
      clearInterval(timerId);
      isRunning = false;
      playAlarm();
      handleTimerComplete();
    }
    updateDisplay();
  }, 250); // 더 자주 체크하여 정확도 향상
}

모드 전환과 자동 진행

작업 타이머가 끝나면 자동으로 휴식 모드로 전환되도록 구현했습니다. 4번의 뽀모도로를 완료하면 긴 휴식으로 넘어갑니다.

function handleTimerComplete() {
  if (currentMode === 'work') {
    pomoCount++;
    document.getElementById('pomo-count').textContent = pomoCount;

    if (pomoCount % 4 === 0) {
      switchMode('long-break');
    } else {
      switchMode('short-break');
    }
  } else {
    switchMode('work');
  }
}

function switchMode(mode) {
  currentMode = mode;
  timeLeft = TIMES[mode];
  updateDisplay();
  document.querySelector('.pomodoro-app')
    .setAttribute('data-mode', mode);

  // 모드 버튼 활성 상태 변경
  document.querySelectorAll('.mode-btn').forEach(btn => {
    btn.classList.toggle('active', btn.dataset.mode === mode);
  });
}

제작 과정에서 배운 점

단순해 보이는 타이머 앱이지만, 실제로 만들면서 많은 것을 배울 수 있었습니다:

  • 상태 관리의 중요성: 타이머가 실행 중인지, 일시정지 상태인지, 어떤 모드인지 등 여러 상태를 관리하는 것이 프로젝트의 핵심이었습니다. 상태가 꼬이면 버그가 발생하기 때문에 명확한 상태 관리가 필수적입니다.
  • 사용자 경험 고려: 버튼의 활성화/비활성화 타이밍, 모드 전환 시 시각적 피드백 등 세세한 UX를 신경 써야 했습니다.
  • 브라우저 API 활용: 알림음 재생을 위한 Web Audio API, 탭 비활성화 감지를 위한 Page Visibility API 등 다양한 브라우저 API를 접하게 되었습니다.
  • 점진적 개선: 처음부터 완벽한 앱을 만들려 하지 않고, 핵심 기능부터 구현한 뒤 하나씩 추가하는 것이 효과적이었습니다.

개인 프로젝트의 가장 큰 장점은 자신이 실제로 사용할 도구를 만든다는 점입니다. 직접 쓰면서 불편한 점을 개선해나가는 과정이 가장 효과적인 학습 방법이라고 느꼈습니다.

최종 완성된 뽀모도로 타이머 앱의 실행 화면

다음 단계로는 localStorage를 활용한 일별 통계 저장, PWA(Progressive Web App) 변환을 통한 오프라인 지원, 그리고 알림 권한을 활용한 브라우저 푸시 알림 기능을 추가할 계획입니다. 작은 프로젝트지만 확장할 수 있는 방향이 무궁무진해서 계속 발전시켜나가고 싶습니다.