3日目のゴールと今日やること
3日目のテーマは
「ストップウォッチの知識を“カウントダウンタイマー”に応用する」
ことです。
キーワードは変わりません。
setTimeout
setInterval
時間管理ロジック
開始 / 停止 / リセット
ミリ秒表示
多重起動防止
ただし今日は、
「0 から増えていくストップウォッチ」ではなく
“指定時間から減っていくカウントダウン”
を作ります。
同じ「時間アプリ」でも、
ロジックの考え方が少し変わるところを
しっかり噛み砕いていきます。
カウントダウンタイマーの考え方をストップウォッチと比べる
ストップウォッチとカウントダウンの違い
ストップウォッチ
→ 0 からスタートして「経過時間」を増やしていく
カウントダウン
→ 指定した時間から「残り時間」を減らしていく
どちらも「時間を扱うアプリ」ですが、
“何を基準にしているか” が違います。
ストップウォッチ:
基準は「スタートした瞬間」
カウントダウン:
基準は「終了予定時刻」
この違いを意識すると、
ロジックがスッと頭に入ります。
終了予定時刻を使う発想
カウントダウンでは、
次のように考えるとシンプルです。
- ユーザーが「10秒」と入力する
- 今の時刻に 10 秒を足して「終了予定時刻」を計算する
- 現在時刻との差を取ることで「残り時間」を求める
コードで書くとこうなります。
const durationMs = 10 * 1000; // 10秒
const endTime = Date.now() + durationMs;
JavaScriptあとは update のたびに、
const remaining = endTime - Date.now();
JavaScriptとすれば、
「残りミリ秒」が常に計算できます。
カウントダウンの基本ロジックを組み立てる
変数の設計
まずは必要な状態を整理します。
let endTime = 0; // 終了予定時刻(ミリ秒)
let remainingMs = 0; // 残り時間(ミリ秒)
let timerId = null; // setInterval の ID
let state = "stopped"; // "stopped" | "running" | "paused"
JavaScriptここでのポイントは、
「残り時間」と「終了予定時刻」を分けて考える
ことです。
running 中は endTime を基準に計算し、
paused 中は remainingMs を保持しておきます。
開始処理の流れ
ユーザーが「秒数」を入力したとします。
const inputSeconds = Number(document.getElementById("secondsInput").value);
const durationMs = inputSeconds * 1000;
JavaScript開始ボタンを押したときの処理はこうなります。
function start() {
if (state === "running") return;
if (state === "stopped") {
const inputSeconds = Number(secondsInput.value);
if (Number.isNaN(inputSeconds) || inputSeconds <= 0) {
showError("1秒以上を入力してください。");
return;
}
clearError();
remainingMs = inputSeconds * 1000;
}
endTime = Date.now() + remainingMs;
timerId = setInterval(update, 10);
state = "running";
}
JavaScriptここで重要なのは、
stopped のときだけ「入力値 → 残り時間」に変換する
paused からの再開では、remainingMs をそのまま使う
という「状態による分岐」です。
update 関数で「残り時間」を計算して表示する
残り時間の計算
update の中身はこうなります。
function update() {
const now = Date.now();
remainingMs = endTime - now;
if (remainingMs <= 0) {
remainingMs = 0;
display(remainingMs);
finish();
return;
}
display(remainingMs);
}
JavaScriptここでやっていることは、
現在時刻との差 → 残り時間
0 以下になったら「終了」とみなす
というシンプルなロジックです。
終了処理
function finish() {
clearInterval(timerId);
state = "stopped";
// ここで音を鳴らしたり、メッセージを出したりできる
}
JavaScript深掘り:なぜ「残り時間を変数に持ち直す」のか?
remainingMs を変数として持っておくことで、
一時停止
再開
リセット
のときに、
「今どれくらい残っているか」を
いつでも参照できます。
停止(一時停止)と再開のロジック
停止(pause)の考え方
停止ボタンを押したときは、
- 今の残り時間を計算して remainingMs に保存
- setInterval を止める
- state を “paused” にする
という流れになります。
function pause() {
if (state !== "running") return;
const now = Date.now();
remainingMs = endTime - now;
if (remainingMs < 0) remainingMs = 0;
clearInterval(timerId);
state = "paused";
}
JavaScript再開の考え方
再開するときは、
- endTime を「今の時刻 + remainingMs」に再計算
- setInterval を再開
- state を “running” に戻す
function resume() {
if (state !== "paused") return;
endTime = Date.now() + remainingMs;
timerId = setInterval(update, 10);
state = "running";
}
JavaScript深掘り:ストップウォッチとの違い
ストップウォッチでは
「累積時間(elapsedBefore)」を足していきましたが、
カウントダウンでは
「残り時間(remainingMs)」を減らしていく
という違いがあります。
ただしどちらも、
running 中は「現在時刻との差」で計算する
paused 中は「途中経過」を変数に保持する
という構造は同じです。
リセットのロジックと UI の一貫性
リセットの動き
リセットは、
タイマーを完全に止める
残り時間を 0 にする
表示を 00:00.000 に戻す
状態を “stopped” に戻す
という動きになります。
function reset() {
clearInterval(timerId);
timerId = null;
remainingMs = 0;
endTime = 0;
state = "stopped";
display(0);
}
JavaScript深掘り:リセットは「状態を初期状態に戻す」操作
リセットは単に「止める」だけではなく、
“アプリを最初の状態に戻す” 操作です。
タイマー系アプリでは、
start / pause / resume / reset
のそれぞれが
「状態をどう変えるか」を
はっきりさせておくことが大事です。
ミリ秒表示をカウントダウンに適用する
表示関数の再利用
ストップウォッチで使った表示関数を
そのままカウントダウンにも使えます。
function display(ms) {
const milliseconds = ms % 1000;
const totalSeconds = Math.floor(ms / 1000);
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60);
const text =
`${String(minutes).padStart(2, "0")}:` +
`${String(seconds).padStart(2, "0")}.` +
`${String(milliseconds).padStart(3, "0")}`;
document.getElementById("time").textContent = text;
}
JavaScript深掘り:ロジックは「経過時間」でも「残り時間」でも同じ
display は
「ミリ秒 → 分・秒・ミリ秒」
に変換しているだけなので、
経過時間(ストップウォッチ)
残り時間(カウントダウン)
どちらにも使えます。
“時間をどう解釈するか”は呼び出し側の責任
という分離ができているのがポイントです。
多重起動防止をカウントダウンにも適用する
状態による制御
start / pause / resume / reset
のそれぞれで、
「どの状態から呼べるか」を決めておきます。
例:
stopped → start だけ有効
running → pause / reset / (ラップなど)
paused → resume / reset
これをコードに落とすとこうなります。
function start() {
if (state !== "stopped") return;
// 入力チェック → remainingMs 設定 → endTime 設定 → setInterval
state = "running";
}
function pause() {
if (state !== "running") return;
// 残り時間計算 → clearInterval
state = "paused";
}
function resume() {
if (state !== "paused") return;
// endTime 再計算 → setInterval
state = "running";
}
function reset() {
if (state === "stopped") return;
// 全部クリア
state = "stopped";
}
JavaScript深掘り:状態マシン的な発想
これはいわゆる
「状態マシン(ステートマシン)」
の考え方です。
状態を文字列で表し、
「どの状態からどの状態に遷移できるか」を
コードで表現しています。
タイマー・ゲーム・フォームウィザードなど、
“段階がある UI” では
この発想がとても役に立ちます。
3日目のまとめコード(重要部分だけ)
let endTime = 0;
let remainingMs = 0;
let timerId = null;
let state = "stopped"; // "stopped" | "running" | "paused"
const secondsInput = document.getElementById("secondsInput");
const timeDisplay = document.getElementById("time");
function start() {
if (state !== "stopped") return;
const inputSeconds = Number(secondsInput.value);
if (Number.isNaN(inputSeconds) || inputSeconds <= 0) {
showError("1秒以上を入力してください。");
return;
}
clearError();
remainingMs = inputSeconds * 1000;
endTime = Date.now() + remainingMs;
timerId = setInterval(update, 10);
state = "running";
}
function update() {
const now = Date.now();
remainingMs = endTime - now;
if (remainingMs <= 0) {
remainingMs = 0;
display(remainingMs);
finish();
return;
}
display(remainingMs);
}
function pause() {
if (state !== "running") return;
const now = Date.now();
remainingMs = endTime - now;
if (remainingMs < 0) remainingMs = 0;
clearInterval(timerId);
state = "paused";
}
function resume() {
if (state !== "paused") return;
endTime = Date.now() + remainingMs;
timerId = setInterval(update, 10);
state = "running";
}
function reset() {
clearInterval(timerId);
timerId = null;
remainingMs = 0;
endTime = 0;
state = "stopped";
display(0);
}
function display(ms) {
const milliseconds = ms % 1000;
const totalSeconds = Math.floor(ms / 1000);
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60);
const text =
`${String(minutes).padStart(2, "0")}:` +
`${String(seconds).padStart(2, "0")}.` +
`${String(milliseconds).padStart(3, "0")}`;
timeDisplay.textContent = text;
}
JavaScript今日いちばん深く理解してほしいこと
カウントダウンタイマーは、
終了予定時刻(endTime)
残り時間(remainingMs)
状態(state)
この 3 つを軸に動いています。
ストップウォッチとカウントダウンは
見た目は似ていても、
「経過時間を増やすか」「残り時間を減らすか」
という発想の違いがあります。
でもどちらも、
Date.now() で時間差を計算する
setInterval で定期的に update する
状態で操作を制御する
という共通の骨格を持っています。
ここまで理解できているあなたは、
もう「時間を扱うアプリ」を
自分で設計できるレベルにいます。

