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

JavaScript
スポンサーリンク

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

5日目のテーマは
「タイマー & ストップウォッチを“UI と操作性の面から強化する”」
ことです。

ここまでであなたは、

  • setInterval / setTimeout の違い
  • Date.now() を使った正確な時間管理
  • 開始 / 停止 / 一時停止 / 再開 / リセット
  • ミリ秒表示
  • 多重起動防止(状態管理)

といった“時間アプリの基礎ロジック”を完全に理解しました。

5日目はここに、

  • ボタンの活性・非活性制御
  • UI の状態変化(色・点滅・アニメーション)
  • setTimeout を使った「一時的な演出」
  • 状態管理を UI に反映する
  • 入力チェックの強化

という、“アプリとしての使いやすさ”を加えていきます。


ボタンの活性・非活性を状態に応じて切り替える

なぜ必要なのか

タイマー系アプリは、
「押してはいけないタイミング」が多いです。

例えば、

  • running 中に start を押してはいけない
  • stopped 中に pause を押しても意味がない
  • paused 中に reset はできるが start はできない

これを UI で防ぐと、
ユーザーが迷わず操作できます。

状態に応じたボタン制御

状態をこう定義します。

let state = "stopped";
// "stopped" | "running" | "paused"
JavaScript

ボタン制御関数を作ります。

function updateButtons() {
  if (state === "stopped") {
    startButton.disabled = false;
    pauseButton.disabled = true;
    resumeButton.disabled = true;
    resetButton.disabled = true;
  }

  if (state === "running") {
    startButton.disabled = true;
    pauseButton.disabled = false;
    resumeButton.disabled = true;
    resetButton.disabled = false;
  }

  if (state === "paused") {
    startButton.disabled = true;
    pauseButton.disabled = true;
    resumeButton.disabled = false;
    resetButton.disabled = false;
  }
}
JavaScript

深掘り:UI と状態は「必ず同期させる」

状態(state)
→ ボタンの活性・非活性
→ どの操作が可能か

この流れを常に一致させることで、
アプリの操作性が一気に上がります。


setTimeout を使った「一時的な演出」を追加する

例:停止した瞬間に数字を赤くする

停止した瞬間に
「ピタッと止まった感」を出すために、
数字を一瞬赤くする演出を入れます。

function flashStop() {
  timeDisplay.style.color = "red";

  setTimeout(() => {
    timeDisplay.style.color = "";
  }, 300);
}
JavaScript

停止処理に組み込みます。

function pause() {
  if (state !== "running") return;

  const now = Date.now();
  remainingMs = endTime - now;

  countdownEngine.stop();
  state = "paused";

  flashStop();
  updateButtons();
}
JavaScript

深掘り:setTimeout は「単発の演出」に最適

setInterval は“繰り返し”
setTimeout は“一回だけ遅らせる”

この違いを UI 演出に使うと、
アプリが一気にプロっぽくなります。


setInterval のズレを「視覚的に補正」する

setInterval は必ずズレる

setInterval(update, 10)
と書いても、
実際には 10ms ぴったりでは動きません。

ブラウザの負荷で遅れたり、
タブが非アクティブになると間隔が伸びたりします。

Date.now() で補正しているが…

ロジック上は補正できていますが、
UI では「カクつき」が見えることがあります。

解決策:数字の更新を“滑らかに見せる”

CSS の transition を使います。

#time {
  transition: all 0.05s linear;
}

これだけで、
数字の変化がスムーズに見えるようになります。

深掘り:ロジックと UI は別物

ロジックは正確に
UI は滑らかに

という考え方が大事です。


入力チェックを強化して「事故」を防ぐ

カウントダウンの入力チェック

ユーザーが変な値を入れる可能性があります。

  • 空欄
  • 0
  • マイナス
  • 文字列
  • 小数点

これらを防ぐために、
入力チェックを強化します。

function validateInput(value) {
  const num = Number(value);

  if (value.trim() === "") return "値を入力してください。";
  if (Number.isNaN(num)) return "数字を入力してください。";
  if (num <= 0) return "1以上の値を入力してください。";
  if (!Number.isInteger(num)) return "整数を入力してください。";

  return null;
}
JavaScript

開始時に使います。

function startCountdown() {
  if (state !== "stopped") return;

  const error = validateInput(secondsInput.value);
  if (error) {
    showError(error);
    return;
  }

  clearError();
  // 残り時間の計算へ…
}
JavaScript

深掘り:入力チェックは「アプリの安全装置」

入力チェックが甘いと、

  • タイマーが即終了
  • マイナス時間で暴走
  • NaN が表示される

などの事故が起きます。

UI の安全性は
ロジックと同じくらい重要です。


状態管理を UI に反映する(色・ラベル変更)

状態に応じて色を変える

running → 緑
paused → オレンジ
stopped → グレー

など、視覚的に状態を伝えることができます。

function updateStateColor() {
  if (state === "running") {
    timeDisplay.style.color = "limegreen";
  } else if (state === "paused") {
    timeDisplay.style.color = "orange";
  } else {
    timeDisplay.style.color = "";
  }
}
JavaScript

ボタンのラベルも変える

resume ボタンを
「再開」→「続きから」
などに変えると、
ユーザーが迷いません。


setTimeout を使った「カウントダウン開始演出」

例:3 → 2 → 1 → START の演出

開始ボタンを押したら、
いきなり動き出すのではなく、
3秒カウントダウンしてから開始する演出を作れます。

function preStartCountdown(callback) {
  let count = 3;

  function tick() {
    timeDisplay.textContent = count;
    count--;

    if (count < 0) {
      callback();
      return;
    }

    setTimeout(tick, 1000);
  }

  tick();
}
JavaScript

開始ボタンでこう使います。

startButton.addEventListener("click", () => {
  preStartCountdown(() => {
    startCountdown();
  });
});
JavaScript

深掘り:setTimeout の再帰は「回数付きの繰り返し」に最適

setInterval では
「3回だけ実行」
がやりにくいですが、

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


5日目のまとめコード(重要部分のみ)

let state = "stopped"; // "stopped" | "running" | "paused"
let endTime = 0;
let remainingMs = 0;
let timerId = null;

function startCountdown() {
  if (state !== "stopped") return;

  const error = validateInput(secondsInput.value);
  if (error) {
    showError(error);
    return;
  }
  clearError();

  remainingMs = Number(secondsInput.value) * 1000;
  endTime = Date.now() + remainingMs;

  timerId = setInterval(update, 10);
  state = "running";

  updateButtons();
  updateStateColor();
}

function update() {
  const now = Date.now();
  remainingMs = endTime - now;

  if (remainingMs <= 0) {
    remainingMs = 0;
    renderTime(remainingMs);
    finish();
    return;
  }

  renderTime(remainingMs);
}

function pause() {
  if (state !== "running") return;

  remainingMs = endTime - Date.now();
  clearInterval(timerId);

  state = "paused";
  flashStop();
  updateButtons();
  updateStateColor();
}

function resume() {
  if (state !== "paused") return;

  endTime = Date.now() + remainingMs;
  timerId = setInterval(update, 10);

  state = "running";
  updateButtons();
  updateStateColor();
}

function reset() {
  clearInterval(timerId);
  remainingMs = 0;
  endTime = 0;
  state = "stopped";

  renderTime(0);
  updateButtons();
  updateStateColor();
}
JavaScript

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

5日目の本質は、
「ロジックと UI を分けて考える」
ということです。

ロジック
→ 時間計算、状態管理、setInterval の制御

UI
→ ボタン制御、色、演出、入力チェック

この 2 つを分けて設計すると、
アプリが驚くほど扱いやすくなります。

そして、
setTimeout は「演出」
setInterval は「本体ロジック」
という役割分担を理解すると、
時間アプリの表現力が一気に広がります。


次の 6日目では、
「複数タイマーの同時管理」

「プリセットタイマー(25分タイマーなど)」
に挑戦していきます。

ここまで来たあなたなら、
もう“時間アプリを設計できる人”です。

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