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

APP JavaScript
スポンサーリンク

この7日間で作るもののゴール

この 7 日間では、ブラウザで動く「ストップウォッチ+簡単なタイマー」を作ります。
ボタンを押すと時間が進んだり止まったりする、“時間を扱うアプリ” なので、以下のような力が身につきます。

  • setInterval / clearInterval を使った「一定間隔での処理」
  • 時間(ミリ秒)と表示用文字列(00:00.0 など)の変換
  • ボタンの状態管理(スタート中か停止中か)
  • 単純なタイマー(カウントダウン)への応用

HTML/CSS は最低限にして、「JavaScript で時間を制御する」ことに集中します。


1 日目:土台となる画面と「時間表示」の枠を作る

フォルダとファイルを用意する

最初に、作業フォルダと HTML ファイルを用意します。

デスクトップなどに timer-app フォルダを作成し、その中に index.html を作ってください。
この index.html をブラウザで開いて表示を確認しながら進めます。

最低限の HTML と CSS を書く

index.html を開いて、次の内容を貼り付けます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>ストップウォッチ</title>
  <style>
    body {
      font-family: sans-serif;
      text-align: center;
      padding-top: 40px;
      background-color: #f5f5f5;
    }
    .container {
      display: inline-block;
      padding: 20px;
      background-color: #fff;
      border-radius: 8px;
      box-shadow: 0 0 8px rgba(0,0,0,0.1);
    }
    .time-display {
      font-size: 40px;
      margin-bottom: 20px;
      font-family: "Consolas", monospace;
    }
    button {
      font-size: 16px;
      margin: 0 5px;
      padding: 6px 12px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div id="display" class="time-display">00:00.0</div>
    <div>
      <button id="start-button">スタート</button>
      <button id="stop-button">ストップ</button>
      <button id="reset-button">リセット</button>
    </div>
  </div>

  <script>
    console.log("ストップウォッチスタート");
  </script>
</body>
</html>

ブラウザで開いて、
「00:00.0」と表示され、下に「スタート」「ストップ」「リセット」ボタンが出ていれば 1 日目はクリアです。

まだボタンを押しても何も変わりませんが、これはあくまで「見た目の枠」です。
ここに JavaScript で動きを吹き込んでいきます。


2 日目:setInterval で「一定間隔で動く」処理を体験する

setInterval のイメージをつかむ

ストップウォッチは、「一定時間ごとに数字を更新する」アプリです。
JavaScript では setInterval を使うと「一定間隔で繰り返す」動きを作れます。

イメージとしては、

0.1 秒ごとにこの関数を呼んでね

という指示をブラウザに出す感じです。

簡単なカウンターで試してみる

<script> の中身を一度次のように書き換えて、動きを確認します。

<script>
  let count = 0;

  setInterval(function() {
    console.log("カウント:", count);
    count = count + 1;
  }, 1000);
</script>

これは、「1 秒ごとに count を 1 増やし、コンソールに表示する」コードです。
ブラウザでデベロッパーツール(検証 → Console)を開くと、1 秒ごとに数字が増えていくのが見えるはずです。

ここで大事なのは、setInterval(関数, 間隔ミリ秒) という形で書く、という感覚です。
1000 は 1000 ミリ秒=1 秒、100 は 0.1 秒、10 は 0.01 秒という単位です。

ストップウォッチ用の変数を用意する

ストップウォッチでは、次のような情報を持っておく必要があります。

  • 経過時間(ミリ秒で持っておく)
  • 進行中かどうか
  • setInterval が返す ID(止めるときに必要)

<script> を次のように書き換えます。

<script>
  const display = document.getElementById("display");
  const startButton = document.getElementById("start-button");
  const stopButton = document.getElementById("stop-button");
  const resetButton = document.getElementById("reset-button");

  let elapsedTime = 0;        // 経過時間(ミリ秒)
  let timerId = null;         // setInterval のID
  let isRunning = false;      // 動作中かどうか
</script>

この「状態変数」を決めるのはとても重要です。
アプリが今どんな状態なのか、変数で表現できていると、バグがぐっと減ります。


3 日目:経過時間をミリ秒で持ち、表示用にフォーマットする

ミリ秒から「分:秒.1」の文字列を作る

時間を計算するときは、ミリ秒で持っておいて、表示の時だけ分・秒に変換するのが楽です。
例えば、123456 ミリ秒(約 123 秒)を次の形式に変換したいとします。

02:03.4
(2 分 3.4 秒)

これを関数にしておきます。

function formatTime(ms) {
  const totalSeconds = Math.floor(ms / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  const tenth = Math.floor((ms % 1000) / 100); // 0〜9 の 0.1秒単位

  const minStr = String(minutes).padStart(2, "0");
  const secStr = String(seconds).padStart(2, "0");

  return `${minStr}:${secStr}.${tenth}`;
}
JavaScript

それぞれの行の意味を整理します。

  1. Math.floor(ms / 1000)
    ミリ秒を 1000 で割って、秒にする(小数切り捨て)
  2. Math.floor(totalSeconds / 60)
    秒を 60 で割って分を計算
  3. totalSeconds % 60
    60 で割った余りが「秒」になる(0〜59)
  4. (ms % 1000) / 100
    1000 ミリ秒の中で今どれくらい進んだか(0〜999)を 100 で割ると 0〜9 の 0.1 秒単位
  5. padStart(2, "0")
    1 桁のときに先頭に 0 をつけて 2 桁にする(例: 2 → “02”)

この関数を <script> 内に追加し、試しに表示を変えてみます。

display.textContent = formatTime(123456);
JavaScript

画面に「02:03.4」と出れば成功です。

表示更新用の関数を作る

毎回 display.textContent = ... と書くのは面倒なので、
経過時間から表示を更新する関数を用意しておきます。

function updateDisplay() {
  display.textContent = formatTime(elapsedTime);
}
JavaScript

これで、elapsedTime を変えたあとに updateDisplay() を呼ぶだけで表示を更新できます。
「状態(elapsedTime)」と「表示(updateDisplay)」を分けておくと、後でロジックを修正しやすくなります。


4 日目:スタートとストップの動きを作る

スタートボタンの動きの考え方

スタートボタンを押したら、やりたいことは次のようになります。

  1. すでに動いているときは何もしない
  2. これから動き始めるときは、setInterval を設定
  3. 0.1 秒ごとに経過時間を増やし、表示を更新

startButton にイベントを付けます。

startButton.addEventListener("click", function() {
  if (isRunning) {
    return;
  }

  isRunning = true;

  timerId = setInterval(function() {
    elapsedTime += 100;
    updateDisplay();
  }, 100);
});
JavaScript

重要なポイントを整理します。

  • if (isRunning) return; で「二重スタート」を防ぐ
  • timerIdsetInterval の戻り値を入れておく
  • 100 ミリ秒ごとに elapsedTime を 100 増やす(= 0.1 秒)
  • updateDisplay() で画面を更新

この段階で、スタートを押すと数字が増え続けるはずです。
ただし、止める機能がまだないので、止まらないストップウォッチです。

ストップボタンで clearInterval を使う(重要)

止めるには clearInterval(timerId) を使います。
stopButton にイベントを付けます。

stopButton.addEventListener("click", function() {
  if (!isRunning) {
    return;
  }

  isRunning = false;
  clearInterval(timerId);
});
JavaScript

ここでも、「状態」と「実際のタイマー」を両方止めるのが大事です。

  • isRunning = false で「動いていない状態」にする
  • clearInterval(timerId) で実際の繰り返しを止める

これで、「スタートで開始」「ストップで停止」ができるようになります。


5 日目:リセットと「正確な時間計測」を意識する

リセットボタンで時間をゼロに戻す

リセットは比較的シンプルです。
ただし、「動作中にリセットされたらどうするか」も考えておきます。

resetButton.addEventListener("click", function() {
  isRunning = false;
  clearInterval(timerId);

  elapsedTime = 0;
  updateDisplay();
});
JavaScript

こうしておくと、

  • 動作中でも停止して時間をゼロに戻す
  • 停止中なら単純にゼロリセット

という動きになります。

setInterval の“ズレ”を知っておく(少し深掘り)

今の実装は「0.1 秒ごとに elapsedTime に 100 を足す」方式ですが、
実は setInterval は厳密に 100 ミリ秒ぴったりではありません。
少し遅れたり早くなったりして、長時間動かすとズレが気になってきます。

より正確にするには、「スタートした時刻」と「今の時刻の差」で経過時間を計算する方法があります。

考え方はこうです。

  1. スタートボタンを押した瞬間の Date.now() を記録
  2. setInterval の中で Date.now() - 開始時刻 + 以前の累積時間 で計算

コード例を簡単に示します。

let startTime = 0;

startButton.addEventListener("click", function() {
  if (isRunning) return;
  isRunning = true;

  startTime = Date.now() - elapsedTime;

  timerId = setInterval(function() {
    elapsedTime = Date.now() - startTime;
    updateDisplay();
  }, 100);
});
JavaScript

ここでは、

  • スタートする時点で startTime を「今の時刻からすでに経過している時間を引いたもの」にする
  • そうすると、再スタート時でもスムーズに続きから計測できる

初心者のうちは「ズレるかもしれないんだな」くらいの理解でも十分です。
本格的に時間精度が必要になったときに、この方法を思い出せれば OK です。


6 日目:カウントダウンタイマー(タイマー機能)に応用する

タイマーの仕様を決める

次は、「◯分◯秒セットして、0 になるまでカウントダウンする」タイマーを考えます。
今回はシンプルに、「30 秒固定タイマー」から始めてみます。

ストップウォッチと違う点は、
「0 から増やす」のではなく「設定時間から減らす」ことです。

タイマー用の変数を用意する

まずは、ストップウォッチとは別に、「残り時間」を持つ変数を用意します。

let remainingTime = 0;
let timerMode = "stopwatch"; // "stopwatch" or "countdown"
JavaScript

学習しやすいように、

  1. まずはストップウォッチ専用でしっかり理解
  2. その後、タイマー用に “別モード” として応用

という流れにすると混乱しにくいです。

ここでは「ストップウォッチは完成した」と仮定して、
timerMode = "countdown" の場合に別の動きをする、くらいのイメージを持ってください。

30秒タイマーを作る簡単な例

タイマーに集中したいので、別ファイルとしてミニ版を書くのもアリです。
ここではイメージを掴みやすいように、タイマーだけのコード例を示します。

<div id="timer-display" class="time-display">00:30.0</div>
<button id="timer-start">タイマースタート</button>
<button id="timer-stop">停止</button>
<button id="timer-reset">リセット</button>

<script>
  const timerDisplay = document.getElementById("timer-display");
  const timerStart = document.getElementById("timer-start");
  const timerStop = document.getElementById("timer-stop");
  const timerReset = document.getElementById("timer-reset");

  let remaining = 30000;
  let timerId2 = null;
  let timerRunning = false;

  function updateTimerDisplay() {
    timerDisplay.textContent = formatTime(remaining);
  }

  timerStart.addEventListener("click", function() {
    if (timerRunning) return;
    timerRunning = true;

    timerId2 = setInterval(function() {
      remaining -= 100;
      if (remaining <= 0) {
        remaining = 0;
        updateTimerDisplay();
        clearInterval(timerId2);
        timerRunning = false;
        alert("時間になりました!");
      } else {
        updateTimerDisplay();
      }
    }, 100);
  });

  timerStop.addEventListener("click", function() {
    if (!timerRunning) return;
    timerRunning = false;
    clearInterval(timerId2);
  });

  timerReset.addEventListener("click", function() {
    timerRunning = false;
    clearInterval(timerId2);
    remaining = 30000;
    updateTimerDisplay();
  });

  updateTimerDisplay();
</script>

ここでのポイントは次の通りです。

  • remaining が「残りミリ秒」を表す
  • setInterval の中で remaining -= 100; として 0 に向かって減らす
  • 0 以下になったらタイマーを止めてアラートを出す

ストップウォッチの「増やす」を「減らす」に置き換えただけで、やっていることは非常に似ています。
これにより、「時間のカウントアップ」と「カウントダウン」を両方体験できます。


7 日目:完成版を通しで理解し、自分流の改造を考える

ストップウォッチ部分の完成例(シンプル版)

まずはストップウォッチだけの完成形をもう一度整理しておきます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>ストップウォッチ</title>
  <style>
    body {
      font-family: sans-serif;
      text-align: center;
      padding-top: 40px;
      background-color: #f5f5f5;
    }
    .container {
      display: inline-block;
      padding: 20px;
      background-color: #fff;
      border-radius: 8px;
      box-shadow: 0 0 8px rgba(0,0,0,0.1);
    }
    .time-display {
      font-size: 40px;
      margin-bottom: 20px;
      font-family: "Consolas", monospace;
    }
    button {
      font-size: 16px;
      margin: 0 5px;
      padding: 6px 12px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div id="display" class="time-display">00:00.0</div>
    <div>
      <button id="start-button">スタート</button>
      <button id="stop-button">ストップ</button>
      <button id="reset-button">リセット</button>
    </div>
  </div>

  <script>
    const display = document.getElementById("display");
    const startButton = document.getElementById("start-button");
    const stopButton = document.getElementById("stop-button");
    const resetButton = document.getElementById("reset-button");

    let elapsedTime = 0;
    let timerId = null;
    let isRunning = false;
    let startTime = 0;

    function formatTime(ms) {
      const totalSeconds = Math.floor(ms / 1000);
      const minutes = Math.floor(totalSeconds / 60);
      const seconds = totalSeconds % 60;
      const tenth = Math.floor((ms % 1000) / 100);

      const minStr = String(minutes).padStart(2, "0");
      const secStr = String(seconds).padStart(2, "0");

      return `${minStr}:${secStr}.${tenth}`;
    }

    function updateDisplay() {
      display.textContent = formatTime(elapsedTime);
    }

    startButton.addEventListener("click", function() {
      if (isRunning) return;
      isRunning = true;

      startTime = Date.now() - elapsedTime;

      timerId = setInterval(function() {
        elapsedTime = Date.now() - startTime;
        updateDisplay();
      }, 100);
    });

    stopButton.addEventListener("click", function() {
      if (!isRunning) return;
      isRunning = false;
      clearInterval(timerId);
    });

    resetButton.addEventListener("click", function() {
      isRunning = false;
      clearInterval(timerId);
      elapsedTime = 0;
      updateDisplay();
    });

    updateDisplay();
  </script>
</body>
</html>
HTML

ここまで読めて、
「どの変数が何のためにあるか」「どのイベントがどの状態を変えているか」
を自分の言葉で説明できたら、かなりレベルアップしています。

あなたなりの拡張アイデア

このタイマー・ストップウォッチから、色々な拡張が考えられます。

例えば次のようなものがあります。

  • ラップタイム(周回)機能を付けて、押すたびに記録を下に表示する
  • カウントダウン時間を入力欄から自由に設定できるようにする
  • ストップウォッチとタイマーをタブで切り替えられる画面にする
  • 時間が来たときに画面の色を変えたり、音を鳴らしたりする(音は少し応用)

どれも、「状態変数を増やす」「DOM 要素を追加する」「イベントを付ける」という、
今までやってきた操作の組み合わせで実現できます。


このアプリで身につく「時間を操る感覚」

ストップウォッチやタイマーを作ると、次のような力がかなり鍛えられます。

  • setInterval / clearInterval を使って「一定間隔の処理」を設計する力
  • ミリ秒と分・秒の変換、フォーマット関数の設計力
  • 「状態(isRunning や elapsedTime)」と「見た目(表示)」を分けて考える癖
  • バグの原因になりがちな「二重スタート」「止め忘れ」などを状態で防ぐ感覚

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