JavaScript | 1 日 120 分 × 7 日アプリ学習:タイマー & ストップウォッチ

JavaScript
スポンサーリンク

6日目のゴールと今日やること

6日目のテーマは
「複数タイマーを同時に扱える“本格的な時間アプリ”を作る」
ことです。

ここまでであなたは、
ストップウォッチ・カウントダウンのロジックを完全に理解し、
状態管理・多重起動防止・ミリ秒表示まで扱えるようになりました。

6日目はここに、

  • 複数タイマーの同時管理
  • タイマーごとの状態管理
  • setInterval の“複数インスタンス化”
  • UI 生成とロジックの分離
  • setTimeout を使った「タイマー終了演出」

を加えて、
「タイマーを量産できる設計」を学びます。


複数タイマーを作るための考え方

タイマーは「1つのオブジェクト」として扱う

1つのタイマーは、
次のような情報を持つ“オブジェクト”として扱うと管理しやすくなります。

{
  id: 1,
  endTime: 0,
  remainingMs: 0,
  state: "stopped",
  timerId: null
}
JavaScript

これを配列で管理します。

const timers = [];
JavaScript

深掘り:タイマーは「状態を持つ小さなアプリ」

1つのタイマーは、
start / pause / resume / reset
という状態遷移を持つ“ミニアプリ”です。

複数タイマーを作るということは、
ミニアプリを複数同時に動かす
ということ。

そのためには、

  • タイマーごとに状態を持つ
  • タイマーごとに setInterval を持つ
  • タイマーごとに UI を持つ

という構造が必要になります。


タイマーを「生成する関数」を作る

タイマーを作る工場(ファクトリー関数)

function createTimer(durationMs) {
  return {
    id: Date.now() + Math.random(),
    endTime: 0,
    remainingMs: durationMs,
    state: "stopped",
    timerId: null
  };
}
JavaScript

タイマーを追加する

function addTimer(durationMs) {
  const timer = createTimer(durationMs);
  timers.push(timer);
  renderTimers();
}
JavaScript

深掘り:ファクトリー関数は「量産のための仕組み」

1つのタイマーを手作業で作るのではなく、
同じ構造のオブジェクトを量産できる仕組み
を作るのが中級者の発想です。


タイマーごとの「開始 / 停止 / リセット」ロジック

開始処理

function startTimer(timer) {
  if (timer.state !== "stopped" && timer.state !== "paused") return;

  timer.endTime = Date.now() + timer.remainingMs;

  timer.timerId = setInterval(() => {
    updateTimer(timer);
  }, 10);

  timer.state = "running";
  renderTimers();
}
JavaScript

更新処理

function updateTimer(timer) {
  timer.remainingMs = timer.endTime - Date.now();

  if (timer.remainingMs <= 0) {
    timer.remainingMs = 0;
    clearInterval(timer.timerId);
    timer.state = "stopped";
    renderTimers();
    finishEffect(timer);
    return;
  }

  renderTimers();
}
JavaScript

停止(pause)

function pauseTimer(timer) {
  if (timer.state !== "running") return;

  timer.remainingMs = timer.endTime - Date.now();
  clearInterval(timer.timerId);
  timer.state = "paused";
  renderTimers();
}
JavaScript

リセット

function resetTimer(timer) {
  clearInterval(timer.timerId);
  timer.remainingMs = 0;
  timer.state = "stopped";
  renderTimers();
}
JavaScript

深掘り:タイマーごとに setInterval を持つ

重要なのは、

timer.timerId がタイマーごとに存在する

ということ。

これにより、

  • タイマー A は動いている
  • タイマー B は停止中
  • タイマー C は残り 5 秒

という状態を同時に扱えます。


タイマー UI を「動的に生成」する

タイマーを描画する

function renderTimers() {
  const area = document.getElementById("timers");
  area.innerHTML = "";

  timers.forEach((timer) => {
    const div = document.createElement("div");
    div.className = "timer";

    const timeText = document.createElement("span");
    timeText.textContent = formatTime(timer.remainingMs);

    const startBtn = document.createElement("button");
    startBtn.textContent = "開始";
    startBtn.onclick = () => startTimer(timer);

    const pauseBtn = document.createElement("button");
    pauseBtn.textContent = "停止";
    pauseBtn.onclick = () => pauseTimer(timer);

    const resetBtn = document.createElement("button");
    resetBtn.textContent = "リセット";
    resetBtn.onclick = () => resetTimer(timer);

    div.appendChild(timeText);
    div.appendChild(startBtn);
    div.appendChild(pauseBtn);
    div.appendChild(resetBtn);

    area.appendChild(div);
  });
}
JavaScript

深掘り:UI は「状態の写し」

UI はあくまで
タイマーオブジェクトの状態を映しているだけ
です。

  • タイマーの状態が変わる
  • renderTimers() を呼ぶ
  • UI が最新状態に更新される

という流れを徹底すると、
複数タイマーでも破綻しません。


setTimeout を使った「終了演出」

タイマー終了時に点滅させる

function finishEffect(timer) {
  const element = document.querySelector(`[data-id="${timer.id}"]`);

  let count = 6;
  function blink() {
    element.classList.toggle("blink");
    count--;
    if (count > 0) {
      setTimeout(blink, 200);
    }
  }

  blink();
}
JavaScript

CSS で blink を定義します。

.blink {
  background-color: yellow;
}

深掘り:setTimeout の再帰は「回数付きアニメーション」に最適

setInterval では
「6回だけ点滅」
のような制御が難しいですが、

setTimeout の再帰なら
「回数 × 間隔」を自由に作れます。


多重起動防止を「タイマーごと」に行う

タイマーごとに state を持つ

if (timer.state === "running") return;
JavaScript

これにより、

  • タイマー A は連打しても 1 回しか動かない
  • タイマー B は別に動かせる

という“独立した動作”が可能になります。

深掘り:状態管理は「複数インスタンス」で真価を発揮する

1つのタイマーなら
isRunning だけで十分ですが、

複数タイマーでは
タイマーごとに状態を持つ
という設計が必須です。


今日いちばん深く理解してほしいこと

6日目の本質は、

  • タイマーは「状態を持つオブジェクト」
  • タイマーごとに setInterval を持つ
  • UI は「状態の写し」
  • setTimeout は「演出」に最適
  • 多重起動防止は「タイマー単位」で行う

という“複数インスタンス設計”です。

ここまで理解できたあなたは、
もう「タイマーアプリを設計できる人」ではなく、
“タイマーアプリを量産できる人” です。


次の 7日目では、
この複数タイマーをさらに発展させて、

  • プリセットタイマー(25分・5分など)
  • タイマーの名前付け
  • ローカルストレージ保存
  • UI の整理と最終仕上げ

を行い、
中級編の集大成に仕上げていきます。

タイトルとURLをコピーしました