HTML、CSS、JavaScriptタイマーアプリWEBアプリ(JavaScript)

【JavaScriptコード解説】タイマーアプリ作成(3/3)

タイマーアプリのアイキャッチ画像03 HTML、CSS、JavaScript

WEBブラウザで動くシンプルなタイマーアプリのコード解説(JavaScript)

前回作成したWEBアプリのコード解説(JavaScript)です。

  • WEBアプリのURL
  • GitHubのURL

コード概要

  • JavaScriptで実装されたタイマーアプリケーション
  • モジュール化された設計(TimerAppTimerUIクラス)
  • イベント駆動型のアーキテクチャ
  • オブジェクト指向プログラミング(OOP)の原則に従った実装
  • ユーザーインターフェース(UI)とタイマーロジックの分離

main.jsの解説

モジュールのインポート

// 必要なモジュールをインポート
import { Timer } from './timer.js';
import { UI } from './ui.js';
  • TimerクラスとUIクラスをそれぞれのファイルからインポートしています。
  • これらのクラスは別のファイルで定義されており、このアプリケーションの主要な機能を提供します。

このコードは、モジュール化されたJavaScriptの特徴を示しています。Timerクラスはタイマーの機能を、UIクラスはユーザーインターフェースの操作を担当します。モジュール化により、コードの管理が容易になり、再利用性も高まります。

TimerAppクラスの定義

class TimerApp {
    constructor() {
        // Timerクラスのインスタンスを作成
        this.timer = new Timer();
        // UIクラスのインスタンスを作成
        this.ui = new UI();
        // イベントリスナーを設定
        this.bindEvents();
    }
        // ...
}
  • TimerAppクラスを定義しています。
  • constructorメソッド内でTimerUIのインスタンスを作成しています。
  • bindEventsメソッドを呼び出してイベントリスナーを設定しています。

このクラスは、アプリケーション全体を管理する中心的な役割を果たします。TimerUIのインスタンスを作成することで、タイマー機能とユーザーインターフェースの操作を統合しています。bindEventsメソッドの呼び出しにより、ユーザーの操作に対する反応を設定しています。

bindEventsメソッド

    // イベントリスナーを設定するメソッド
    bindEvents() {
        // スタートボタンのクリックイベントを設定
        document.getElementById('startBtn').addEventListener('click', () => this.startTimer());
        // ストップボタンのクリックイベントを設定
        document.getElementById('stopBtn').addEventListener('click', () => this.stopTimer());
        // リセットボタンのクリックイベントを設定
        document.getElementById('resetBtn').addEventListener('click', () => this.resetTimer());

        // タイマーの毎秒更新時のコールバックを設定
        this.timer.onTick = (remainingSeconds) => this.ui.updateTimerDisplay(remainingSeconds);
        // タイマー完了時のコールバックを設定
        this.timer.onComplete = () => {
            this.ui.playAlarm(); // アラームを鳴らす
            this.ui.updateButtonStates(false, true); // ボタンの状態を更新
        };
    }
  • ボタンのクリックイベントにメソッドを紐付けています。
  • タイマーのonTickonCompleteイベントにコールバック関数を設定しています。

このメソッドは、ユーザーインターフェースとタイマー機能を結びつける重要な役割を果たします。ボタンクリックに対する動作を定義し、タイマーの進行や完了時の動作を設定しています。アロー関数を使用することで、thisのコンテキストを適切に保持しています。

startTimerメソッド

    // タイマーを開始するメソッド
    startTimer() {
        // タイマーが動作中でなく、アラームも鳴っていない場合
        if (!this.timer.isActive() && !this.ui.isAlarmActive()) {
            // 入力された時間を取得
            const { hours, minutes, seconds } = this.ui.getInputValues();
            // 合計秒数を計算
            const totalSeconds = hours * 3600 + minutes * 60 + seconds;

            // 時間が設定されていない場合はエラーメッセージを表示
            if (totalSeconds === 0) {
                this.ui.showError('タイマーの時間を設定してください');
                return;
            }

            // タイマーが未設定の場合、新しい時間をセット
            if (this.timer.getRemainingSeconds() === 0) {
                this.timer.setTime(totalSeconds);
            }
            
            // タイマーを開始
            this.timer.start();
            // UIのボタン状態を更新
            this.ui.updateButtonStates(true);
        }
    }
  • タイマーとアラームの状態をチェックしています。
  • 入力された時間を取得し、秒数に変換しています。
  • 時間が設定されていない場合はエラーメッセージを表示します。
  • タイマーを開始し、UIの状態を更新します。

このメソッドは、タイマーを開始する際の一連の処理を行います。ユーザーの入力を検証し、適切な条件下でのみタイマーを開始する安全な設計になっています。分割代入を使用して入力値を取得し、早期リターンでエラー処理を行っているのがポイントです。

stopTimerメソッド

    // タイマーを停止するメソッド
    stopTimer() {
        // アラームが鳴っている場合はアラームを停止
        if (this.ui.isAlarmActive()) {
            this.ui.stopAlarm();
        } else {
            // そうでなければタイマーを停止
            this.timer.stop();
        }
        // UIのボタン状態を更新
        this.ui.updateButtonStates(false);
    }
  • アラームが鳴っている場合はアラームを停止します。
  • そうでない場合はタイマーを停止します。
  • UIのボタン状態を更新します。

このメソッドは、タイマーの停止とアラームの停止を兼ねています。状況に応じて適切な動作を選択する条件分岐が使われており、ユーザーの意図に沿った動作を実現しています。

resetTimerメソッド

    // タイマーをリセットするメソッド
    resetTimer() {
        // アラームが鳴っていない場合のみリセット
        if (!this.ui.isAlarmActive()) {
            // タイマーをリセット
            this.timer.reset();
            // 入力フィールドをリセット
            this.ui.resetInputs();
            // タイマー表示を0にリセット
            this.ui.updateTimerDisplay(0);
        }
    }
}
  • アラームが鳴っていない場合のみリセット処理を行います。
  • タイマーをリセットし、入力フィールドとタイマー表示をクリアします。

このメソッドは、タイマーを初期状態に戻す役割を果たします。アラームが鳴っている間はリセットできないようにすることで、ユーザーが誤ってタイマーをリセットしてしまうことを防いでいます。

DOMContentLoadedイベントリスナー

// DOMの読み込みが完了したらTimerAppのインスタンスを作成
document.addEventListener('DOMContentLoaded', () => {
    new TimerApp();
});
  • DOMの読み込みが完了したらTimerAppのインスタンスを作成します。

このコードは、ページの読み込みが完了したタイミングでアプリケーションを初期化します。DOMContentLoadedイベントを使用することで、HTMLの解析が完了し、DOMツリーが構築された後にJavaScriptが実行されることを保証しています。これにより、DOM要素への安全なアクセスが可能になります。

timer.jsの解説

クラス定義と constructor メソッド

export class Timer {
    constructor() {
        // setIntervalの戻り値を保持するための変数
        this.intervalId = null;
        // 残り時間(秒)
        this.remainingSeconds = 0;
        // タイマーが動作中かどうかのフラグ
        this.isRunning = false;
        // 毎秒の更新時に呼び出されるコールバック関数
        this.onTick = null;
        // タイマー完了時に呼び出されるコールバック関数
        this.onComplete = null;
    }
}
  • export class Timer: Timerクラスを定義し、外部からインポート可能にします。
  • constructor(): クラスのインスタンスが作成されるときに自動的に呼び出されるメソッドです。

このコードでは、Timerクラスを定義し、そのconstructorメソッド内でクラスの初期状態を設定しています。intervalIdはsetIntervalの戻り値を保持するための変数、remainingSecondsは残り時間(秒)、isRunningはタイマーが動作中かどうかのフラグ、onTickonCompleteはそれぞれコールバック関数を保持するための変数です。これらの変数は全てクラスのプロパティとして初期化されています。

start メソッド

    // タイマーを開始するメソッド
    start() {
        // すでに動作中の場合は何もしない
        if (this.isRunning) return;

        // タイマーを動作中に設定
        this.isRunning = true;
        // 1秒ごとにtickメソッドを呼び出す
        this.intervalId = setInterval(() => this.tick(), 1000);
    }
  • タイマーを開始するメソッドです。
  • すでに動作中の場合は何もしません。
  • タイマーを動作中に設定し、1秒ごとにtickメソッドを呼び出します。

このメソッドは、タイマーを開始する役割を果たします。まず、タイマーがすでに動作中でないかをチェックし、動作中でなければタイマーを開始します。setInterval関数を使用して1秒(1000ミリ秒)ごとにtickメソッドを呼び出すように設定し、その戻り値(intervalID)をthis.intervalIdに保存します。これにより、後でタイマーを停止する際にこのIDを使用できます。

stop メソッド

    // タイマーを停止するメソッド
    stop() {
        // 動作中でない場合は何もしない
        if (!this.isRunning) return;

        // タイマーを停止状態に設定
        this.isRunning = false;
        // setIntervalをクリア
        clearInterval(this.intervalId);
        this.intervalId = null;
    }
  • タイマーを停止するメソッドです。
  • 動作中でない場合は何もしません。
  • タイマーを停止状態に設定し、setIntervalをクリアします。

stopメソッドは、実行中のタイマーを停止させる役割を果たします。まず、タイマーが動作中であるかをチェックし、動作中でなければ何もしません。タイマーが動作中の場合、isRunningフラグをfalseに設定し、clearInterval関数を使用して定期的なtickメソッドの呼び出しを停止します。最後に、intervalIdをnullに設定して、タイマーが完全に停止したことを示します。

reset メソッド

    // タイマーをリセットするメソッド
    reset() {
        // タイマーを停止
        this.stop();
        // 残り時間を0にリセット
        this.remainingSeconds = 0;
        // 更新を通知
        this.notifyTick();
    }
  • タイマーをリセットするメソッドです。
  • タイマーを停止し、残り時間を0にリセットします。
  • 更新を通知します。

resetメソッドは、タイマーを初期状態に戻す役割を果たします。まずstopメソッドを呼び出してタイマーを停止し、次に残り時間(remainingSeconds)を0にリセットします。最後にnotifyTickメソッドを呼び出して、タイマーの状態が更新されたことを通知します。これにより、タイマーの表示などを更新することができます。

tick メソッド

    // 1秒ごとに呼び出されるメソッド
    tick() {
        if (this.remainingSeconds > 0) {
            // 残り時間がある場合は1秒減らす
            this.remainingSeconds--;
            // 更新を通知
            this.notifyTick();
        } else {
            // 残り時間が0になったらタイマーを停止し、完了を通知
            this.stop();
            this.notifyComplete();
        }
    }
  • 1秒ごとに呼び出されるメソッドです。
  • 残り時間がある場合は1秒減らし、更新を通知します。
  • 残り時間が0になったらタイマーを停止し、完了を通知します。

tickメソッドは、タイマーの中核となる部分で、1秒ごとに呼び出されます。残り時間(remainingSeconds)が0より大きい場合、それを1減らし、notifyTickメソッドを呼び出して更新を通知します。残り時間が0になった場合は、stopメソッドを呼び出してタイマーを停止し、notifyCompleteメソッドを呼び出してタイマーが完了したことを通知します。

setTime メソッド

    // タイマーの時間を設定するメソッド
    setTime(seconds) {
        this.remainingSeconds = seconds;
        // 更新を通知
        this.notifyTick();
    }
  • タイマーの時間を設定するメソッドです。
  • 指定された秒数を残り時間として設定します。
  • 更新を通知します。

setTimeメソッドは、タイマーの残り時間を設定するために使用されます。引数として渡された秒数をremainingSecondsプロパティに設定し、notifyTickメソッドを呼び出して更新を通知します。これにより、新しく設定された時間がすぐに反映されます。

getRemainingSeconds メソッド

    // 残り時間を取得するメソッド
    getRemainingSeconds() {
        return this.remainingSeconds;
    }
  • 残り時間を取得するメソッドです。
  • 現在の残り時間(秒)を返します。

このメソッドは非常にシンプルで、現在の残り時間(remainingSeconds)の値をそのまま返します。これにより、タイマーの外部から現在の残り時間を確認することができます。

isActive メソッド

    // タイマーが動作中かどうかを確認するメソッド
    isActive() {
        return this.isRunning;
    }
  • タイマーが動作中かどうかを確認するメソッドです。
  • タイマーが動作中であればtrue、そうでなければfalseを返します。

isActiveメソッドも非常にシンプルで、isRunningフラグの値をそのまま返します。これにより、タイマーが現在動作中かどうかを外部から確認することができます。

notifyTick メソッド

    // 更新を通知するメソッド
    notifyTick() {
        // onTickが関数として設定されている場合に呼び出す
        if (typeof this.onTick === 'function') {
            this.onTick(this.remainingSeconds);
        }
    }
  • 更新を通知するメソッドです。
  • onTickが関数として設定されている場合に呼び出します。

notifyTickメソッドは、タイマーの状態が更新されたことを通知するために使用されます。onTickプロパティが関数として設定されている場合、その関数を呼び出し、現在の残り時間(remainingSeconds)を引数として渡します。これにより、タイマーの状態が変わるたびに特定の処理(例えば、表示の更新など)を行うことができます。

notifyComplete メソッド

    // タイマー完了を通知するメソッド
    notifyComplete() {
        // onCompleteが関数として設定されている場合に呼び出す
        if (typeof this.onComplete === 'function') {
            this.onComplete();
        }
    }
  • タイマー完了を通知するメソッドです。
  • onCompleteが関数として設定されている場合に呼び出します。

notifyCompleteメソッドは、タイマーが完了(残り時間が0になった)ときに呼び出されます。onCompleteプロパティが関数として設定されている場合、その関数を呼び出します。これにより、タイマーが完了したときに特定の処理(例えば、アラームを鳴らすなど)を行うことができます。

以上が、このTimerクラスの詳細な解説です。このクラスを使用することで、簡単にカウントダウンタイマーを実装し、その状態を管理することができます。

ui.jsの解説

クラス定義と constructor

export class UI {
    constructor() {
        // HTML要素の参照を取得
        this.timerElement = document.getElementById('timer');
        this.startBtn = document.getElementById('startBtn');
        this.stopBtn = document.getElementById('stopBtn');
        this.resetBtn = document.getElementById('resetBtn');
        this.hoursInput = document.getElementById('hours');
        this.minutesInput = document.getElementById('minutes');
        this.secondsInput = document.getElementById('seconds');
        this.alarmAudio = document.getElementById('alarmSound');
        
        // アラームの再生状態を管理するフラグ
        this.isAlarmPlaying = false;

        // UIの初期化
        this.initializeUI();
    }
}
  • UIクラスを定義し、exportキーワードで外部から使用可能にしています。
  • constructorメソッド内で、HTML要素の参照を取得し、クラスのプロパティとして保存しています。
  • isAlarmPlayingフラグを初期化し、initializeUIメソッドを呼び出してUIを初期化しています。

このコードは、UIクラスの基本構造を定義しています。constructorは、クラスのインスタンスが作成されたときに自動的に実行される特別なメソッドです。ここでは、必要なHTML要素の参照を取得し、初期設定を行っています。document.getElementByIdメソッドを使用して、HTMLの各要素を JavaScript から操作できるようにしています。

initializeUI メソッド

    // UIの初期状態を設定するメソッド
    initializeUI() {
        this.updateTimerDisplay(0);
        this.updateButtonStates(false);
    }
  • タイマーの表示を0に設定します。
  • ボタンの状態を初期状態(タイマーが動作していない状態)に設定します。

このメソッドは、UIの初期状態を設定するために使用されます。タイマーを0に設定し、ボタンの状態を適切に設定することで、アプリケーションの起動時に正しい表示状態を確保します。

updateTimerDisplay メソッド

    // タイマーの表示を更新するメソッド
    updateTimerDisplay(totalSeconds) {
        // 時、分、秒に変換
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;
        // フォーマットして表示
        this.timerElement.textContent = `${this.padZero(hours)}:${this.padZero(minutes)}:${this.padZero(seconds)}`;
    }
  • 総秒数から時間を計算します。
  • 計算結果をpadZeroメソッドでフォーマットし、タイマー表示を更新します。

このメソッドは、与えられた総秒数をもとにタイマーの表示を更新します。Math.floorを使用して小数点以下を切り捨て、%演算子を使用して余りを計算することで、適切な時間、分、秒を算出しています。padZeroメソッドを使用して各数値を2桁の文字列に変換し、見やすい形式で表示します。

updateButtonStates メソッド

    // ボタンと入力フィールドの状態を更新するメソッド
    updateButtonStates(isRunning, isAlarmActive = false) {
        // ボタンの有効/無効を設定
        this.startBtn.disabled = isRunning || isAlarmActive;
        this.stopBtn.disabled = !isRunning && !isAlarmActive;
        this.resetBtn.disabled = isRunning || isAlarmActive;
        // 入力フィールドの有効/無効を設定
        [this.hoursInput, this.minutesInput, this.secondsInput].forEach(input => input.disabled = isRunning || isAlarmActive);
    }
  • タイマーの実行状態とアラームの状態に基づいて、各ボタンと入力フィールドのdisabledプロパティを設定します。
  • forEachメソッドを使用して、すべての入力フィールドに同じ処理を適用します。

このメソッドは、アプリケーションの現在の状態に基づいてUIの各要素を適切に有効/無効化します。タイマーが動作中やアラームが鳴っている間は、特定の操作を防ぐためにボタンや入力フィールドを無効化します。これにより、ユーザーが不適切なタイミングで操作することを防ぎ、アプリケーションの安定性を向上させます。

showError メソッド

    // エラーメッセージを表示するメソッド
    showError(message) {
        alert(message);
    }
  • alert関数を使用してエラーメッセージを表示します。

このシンプルなメソッドは、ユーザーにエラーメッセージを表示するために使用されます。alert関数は、ブラウザの標準的なダイアログボックスを表示し、ユーザーの注意を引くのに効果的です。

getInputValues メソッド

    // 入力フィールドの値を取得するメソッド
    getInputValues() {
        return {
            hours: parseInt(this.hoursInput.value, 10) || 0,
            minutes: parseInt(this.minutesInput.value, 10) || 0,
            seconds: parseInt(this.secondsInput.value, 10) || 0
        };
    }
  • 各入力フィールドの値を整数に変換します。
  • 変換できない場合や空の場合は0を使用します。
  • 時間、分、秒の値をオブジェクトとして返します。

このメソッドは、ユーザーが入力したタイマーの設定値を取得し、使いやすい形式に変換します。parseInt関数を使用して文字列を整数に変換し、|| 演算子を使用してNaNや未定義の値を0に置き換えています。これにより、常に有効な数値が返されることが保証されます。

resetInputs メソッド

    // 入力フィールドをリセットするメソッド
    resetInputs() {
        this.hoursInput.value = '0';
        this.minutesInput.value = '0';
        this.secondsInput.value = '0';
    }
  • すべての入力フィールドの値を‘0’にリセットします。

このメソッドは、タイマーをリセットする際に入力フィールドをクリアするために使用されます。各フィールドに直接’0’を設定することで、ユーザーインターフェースを初期状態に戻します。

playAlarm メソッド

    // アラーム音を再生するメソッド
    playAlarm() {
        if (this.alarmAudio) {
            this.alarmAudio.loop = true; // ループ再生を設定
            this.alarmAudio.play().catch(error => console.error('Alarm playback failed:', error));
            this.isAlarmPlaying = true;
            this.updateButtonStates(false, true);
        } else {
            console.error('Alarm audio element not found');
        }
    }
  • アラーム音をループ再生に設定します。
  • playメソッドでアラームを再生し、エラーがあればコンソールに出力します。
  • アラーム再生状態をtrueに設定し、ボタンの状態を更新します。

このメソッドは、タイマーが終了したときにアラーム音を再生するために使用されます。loopプロパティをtrueに設定することで、ユーザーが停止するまでアラームが繰り返し再生されます。playメソッドはPromiseを返すため、catchを使用してエラーハンドリングを行っています。

stopAlarm メソッド

    // アラーム音を停止するメソッド
    stopAlarm() {
        if (this.alarmAudio) {
            this.alarmAudio.pause();
            this.alarmAudio.currentTime = 0; // 再生位置を先頭に戻す
            this.isAlarmPlaying = false;
            this.updateButtonStates(false);
        }
    }
  • アラーム音を一時停止し、再生位置を先頭に戻します。
  • アラーム再生状態をfalseに設定し、ボタンの状態を更新します。

このメソッドは、ユーザーがアラームを停止したときに呼び出されます。pauseメソッドでアラームを停止し、currentTimeを0に設定することで、次回再生時に最初から再生されるようにしています。また、UIの状態を適切に更新することで、ユーザーが次のアクションを取れるようにします。

isAlarmActive メソッド

    // アラームが鳴っているかどうかを確認するメソッド
    isAlarmActive() {
        return this.isAlarmPlaying;
    }
  • アラームが現在再生中かどうかを示すブール値を返します。

このシンプルなメソッドは、アラームの現在の状態を外部から確認するために使用されます。これにより、他のコンポーネントがアラームの状態に基づいて動作を調整することができます。

padZero メソッド

    // 数字を2桁の文字列に変換するメソッド(例: 1 → "01")
    padZero(num) {
        return num.toString().padStart(2, '0');
    }
  • 数値を文字列に変換し、2桁になるように先頭に0を追加します。

このユーティリティメソッドは、タイマーの表示を整形するために使用されます。padStartメソッドを使用して、1桁の数字の場合に先頭に0を追加することで、常に2桁の表示を維持します。これにより、タイマーの表示が一貫性を保ち、読みやすくなります。

まとめ

コードの特徴

  • モジュール化オブジェクト指向設計により、保守性と再利用性を向上
  • イベント駆動型アーキテクチャとコールバック関数を用いた効率的な実装
  • ES6+の機能を活用し、カプセル化と関心の分離を実現
  • アクセシビリティユーザビリティを考慮したUI設計(ボタン状態管理、音声アラーム)
  • 早期リターンによるエラー処理とsetInterval/clearIntervalを用いた効率的なタイマー制御
  • DOMContentLoadedイベントによる適切な初期化と時間計算の適切な実装

このJavaScriptコードは、モダンな Web アプリケーション開発の原則に従って設計されたタイマーアプリケーションを実装しています。モジュール化されたアーキテクチャにより、コードの保守性拡張性が高められています。

TimerAppクラスがアプリケーション全体を統括し、Timerクラスがタイマーのロジックを、UIクラスがユーザーインターフェースの管理を担当するという明確な責任分担が行われています。

イベント駆動型の設計により、ユーザーの操作に対してリアクティブに応答する仕組みが実現されています。また、コールバック関数を活用することで、コンポーネント間の疎結合な通信が可能となっています。

UI面では、アクセシビリティに配慮したボタンの状態管理や、エラーハンドリングによるユーザーフレンドリーな設計が特徴的です。さらに、音声アラーム機能の実装により、視覚以外の通知手段も提供されています。

全体として、このコードはモダンなJavaScript開発保守性拡張性ユーザビリティに優れたアプリケーションの基盤を提供しています。オブジェクト指向プログラミングの原則に従いつつ、関数型プログラミングの要素も取り入れたバランスの取れた設計となっています。

早期リターン

早期リターンを用いたエラー処理の箇所の例

Timer クラスの start メソッド:

start() {
    if (this.isRunning) return;
    // ... 以下省略
}

特定の条件が満たされない場合に早期にメソッドから抜け出すことで、不要な処理を避けています。これにより、コードの可読性が向上し、エラーの可能性を減らすことができます。

setIntervalclearInterval
  1. setInterval:
javascriptthis.intervalId = setInterval(() => this.tick(), 1000);

Timerクラスのstartメソッド内で使用されています。

  1. clearInterval:
javascriptclearInterval(this.intervalId);

Timerクラスのstopメソッド内で使用されています。

これらの使用によるメリットは以下の通りです:

  1. 定期的な処理の実現:
    setIntervalを使用することで、1秒ごとにtickメソッドを呼び出し、タイマーの残り時間を更新しています。これにより、正確な時間間隔でタイマーを動作させることができます。
  2. 柔軟な制御:
    clearIntervalを使用することで、タイマーの停止を簡単に実装できます。ユーザーがタイマーを途中で停止したい場合に対応できます。
  3. リソースの効率的な管理:
    タイマーが不要になった時点でclearIntervalを呼び出すことで、不要な処理を停止し、システムリソースを節約できます。
  4. コードの可読性と保守性の向上:
    setIntervalとclearIntervalを使用することで、タイマーの開始と停止の処理が明確になり、コードの意図が理解しやすくなります。
  5. 非同期処理の実現:
    setIntervalを使用することで、他の処理を妨げることなくバックグラウンドでタイマー処理を実行できます。

コメント

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