WEBブラウザで動くシンプルなタイマーアプリ作成
今回は、HTML、CSS、JavaScriptを使用して簡単なタイマーアプリを作成します。
- タイマーアプリ(1/3)
- タイマーアプリ(2/3)
- タイマーアプリ(3/3)
- WEBアプリのURL
- GitHubのURL
GitHubのPagesで公開しているのでタイマーアプリを使用できます。
アプリ概要
- シンプルで使いやすいタイマーアプリケーション
- HTML、CSS、JavaScriptで構築されたウェブベースのアプリ
- ユーザーが時間、分、秒を設定し、カウントダウンを行う機能
- タイマー終了時にアラームが鳴る機能
- 開始、停止、リセットの基本的な操作が可能
VSCodeのインストールがまだの方はこちらをご覧ください。
VSCodeで作ってみよう!
まず「VSCode」を起動します。
左上の「ファイル」をクリックして「フォルダを開く」を選択します。
エクスプローラー内で作りたい場所を決め、「新しいフォルダー」をクリックして新規フォルダ「TimerApp」を作成します。
ポップが出ますが、自分で作成したフォルダなので「はい、作成者を信頼します」をクリックします。
このような階層でフォルダとファイルを作成していきます。
TimerApp/
│
├── index.html
│
├── css/
│ └── styles.css
│
├── js/
│ ├── main..js
│ ├── timer.js
│ └── ui.js
│
├── assets/
├── alarm.mp3
「TimerApp」の横にあるの新規フォルダと新規ファイルのマークをクリックして「css」「js」「assets」フォルダを作成してください。次にフォルダ内に以下のファイルを作成してください。
アラーム音の準備
今回は「DOVA-SYNDROME」からダウンロードします。ログインしなくても使用できます。
上記のURLのページの「音楽素材ダウンロードページへ」をクリックします。
次のページの以下の場所をクリックするとダウンロードできます。
「alarm.mp3」にファイル名を変更します。
ファイルが作成出来たら、以下の完成コードを対応するファイルにコピペしてください。
コードの細かい解説は次のページで行いますのでとりあえず動かしてみましょう。
- ファイル名の箇所をクリックすると各コードが表示されます。
- コードの右上にコピーマークがあります。
HTML (index.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 文書の文字エンコーディングを指定 -->
<meta charset="UTF-8">
<!-- レスポンシブデザイン用のビューポート設定 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 検索エンジン用の説明文 -->
<meta name="description" content="シンプルで使いやすいタイマーアプリケーション">
<!-- ページのタイトル -->
<title>シンプルタイマー</title>
<!-- CSSファイルのリンク -->
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<!-- メインコンテンツを囲むコンテナ -->
<div class="container">
<!-- ページの見出し -->
<h1>シンプルタイマー</h1>
<!-- タイマーの表示領域(aria-liveは画面読み上げ用) -->
<div id="timer" aria-live="polite">00:00:00</div>
<!-- 時間入力フォーム -->
<div class="input-container">
<!-- 時間の入力フィールド -->
<label for="hours">時間:</label>
<input type="number" id="hours" min="0" max="23" value="0" aria-label="時間">
<!-- 分の入力フィールド -->
<label for="minutes">分:</label>
<input type="number" id="minutes" min="0" max="59" value="0" aria-label="分">
<!-- 秒の入力フィールド -->
<label for="seconds">秒:</label>
<input type="number" id="seconds" min="0" max="59" value="0" aria-label="秒">
</div>
<!-- ボタン群 -->
<div class="button-container">
<!-- タイマー開始ボタン -->
<button id="startBtn" aria-label="タイマー開始">開始</button>
<!-- タイマー停止ボタン(初期状態では無効) -->
<button id="stopBtn" disabled aria-label="タイマー停止">停止</button>
<!-- タイマーリセットボタン -->
<button id="resetBtn" aria-label="タイマーリセット">リセット</button>
</div>
</div>
<!-- アラーム音声ファイル(事前に読み込み) -->
<audio id="alarmSound" src="assets/alarm.mp3" preload="auto"></audio>
<!-- JavaScriptファイルの読み込み(モジュールとして) -->
<script type="module" src="js/main.js"></script>
</body>
</html>
CSS (styles.css)
/* グローバル変数の定義 */
:root {
--primary-color: #4a90e2; /* メインカラー(ボタンなどに使用) */
--secondary-color: #f0f0f0; /* 背景色 */
--text-color: #333; /* 通常のテキストカラー */
--button-hover-color: #357abd; /* ボタンにマウスを乗せたときの色 */
--button-disabled-color: #cccccc; /* 無効化されたボタンの色 */
}
/* すべての要素に対する基本設定 */
* {
box-sizing: border-box; /* パディングとボーダーを要素のサイズに含める */
margin: 0; /* 外側の余白をゼロに */
padding: 0; /* 内側の余白をゼロに */
}
/* ページ全体のスタイル設定 */
body {
font-family: 'Arial', sans-serif; /* フォントファミリーの指定 */
display: flex; /* フレックスボックスレイアウトを使用 */
justify-content: center; /* 水平方向の中央揃え */
align-items: center; /* 垂直方向の中央揃え */
min-height: 100vh; /* 最小の高さをビューポートの高さに設定 */
background-color: var(--secondary-color); /* 背景色の設定 */
color: var(--text-color); /* テキストの色を設定 */
}
/* メインコンテンツを囲むコンテナのスタイル */
.container {
background-color: white; /* 背景を白に */
padding: 2rem; /* 内側の余白を設定 */
border-radius: 10px; /* 角を丸くする */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 影をつけて立体感を出す */
text-align: center; /* テキストを中央揃えに */
}
/* 見出し(h1)のスタイル */
h1 {
margin-bottom: 1rem; /* 下側の余白を設定 */
color: var(--primary-color); /* メインカラーを使用 */
}
/* タイマー表示部分のスタイル */
#timer {
font-size: 3rem; /* 大きめのフォントサイズ */
margin: 1rem 0; /* 上下の余白を設定 */
font-weight: bold; /* 太字に */
}
/* 時間入力部分を囲むコンテナのスタイル */
.input-container {
display: flex; /* フレックスボックスレイアウトを使用 */
justify-content: center; /* 中央揃え */
align-items: center; /* 垂直方向も中央揃え */
margin-bottom: 1rem; /* 下側の余白を設定 */
}
/* 時間入力フィールドのスタイル */
.input-container input {
width: 50px; /* 幅を固定 */
text-align: center; /* テキストを中央揃え */
margin: 0 0.5rem; /* 左右の余白を設定 */
padding: 0.5rem; /* 内側の余白を設定 */
border: 1px solid #ccc; /* 枠線を設定 */
border-radius: 4px; /* 角を少し丸く */
}
/* 入力フィールドのラベルのスタイル */
.input-container label {
font-size: 0.9rem; /* フォントサイズを少し小さく */
}
/* ボタンを囲むコンテナのスタイル */
.button-container {
display: flex; /* フレックスボックスレイアウトを使用 */
justify-content: center; /* 中央揃え */
gap: 0.5rem; /* ボタン間の間隔を設定 */
}
/* ボタンの基本スタイル */
button {
font-size: 1rem; /* フォントサイズ */
padding: 0.5rem 1rem; /* 内側の余白 */
cursor: pointer; /* マウスカーソルをポインターに */
background-color: var(--primary-color); /* 背景色 */
color: white; /* テキストを白に */
border: none; /* 枠線を消す */
border-radius: 4px; /* 角を丸く */
transition: background-color 0.3s ease; /* 背景色の変化をなめらかに */
}
/* ホバー時のボタンスタイル(無効化されていない場合) */
button:hover:not(:disabled) {
background-color: var(--button-hover-color); /* ホバー時の色に変更 */
}
/* 無効化されたボタンのスタイル */
button:disabled {
cursor: not-allowed; /* カーソルを禁止マークに */
opacity: 0.6; /* 透明度を下げて薄く表示 */
background-color: var(--button-disabled-color); /* 無効時の色に変更 */
}
/* レスポンシブデザイン: 画面幅が480px以下の場合のスタイル */
@media (max-width: 480px) {
.container {
width: 90%; /* コンテナの幅を画面の90%に */
padding: 1rem; /* パディングを小さく */
}
#timer {
font-size: 2rem; /* タイマーの文字サイズを小さく */
}
.input-container {
flex-direction: column; /* 入力フィールドを縦に並べる */
}
.input-container input {
margin: 0.5rem 0; /* 上下の余白を設定 */
}
}
JavaScript (main.js、timer.js、ui.js)
// 必要なモジュールをインポート
import { Timer } from './timer.js';
import { UI } from './ui.js';
// タイマーアプリケーションのメインクラス
class TimerApp {
constructor() {
// Timerクラスのインスタンスを作成
this.timer = new Timer();
// UIクラスのインスタンスを作成
this.ui = new UI();
// イベントリスナーを設定
this.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); // ボタンの状態を更新
};
}
// タイマーを開始するメソッド
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);
}
}
// タイマーを停止するメソッド
stopTimer() {
// アラームが鳴っている場合はアラームを停止
if (this.ui.isAlarmActive()) {
this.ui.stopAlarm();
} else {
// そうでなければタイマーを停止
this.timer.stop();
}
// UIのボタン状態を更新
this.ui.updateButtonStates(false);
}
// タイマーをリセットするメソッド
resetTimer() {
// アラームが鳴っていない場合のみリセット
if (!this.ui.isAlarmActive()) {
// タイマーをリセット
this.timer.reset();
// 入力フィールドをリセット
this.ui.resetInputs();
// タイマー表示を0にリセット
this.ui.updateTimerDisplay(0);
}
}
}
// DOMの読み込みが完了したらTimerAppのインスタンスを作成
document.addEventListener('DOMContentLoaded', () => {
new TimerApp();
});
export class Timer {
constructor() {
// setIntervalの戻り値を保持するための変数
this.intervalId = null;
// 残り時間(秒)
this.remainingSeconds = 0;
// タイマーが動作中かどうかのフラグ
this.isRunning = false;
// 毎秒の更新時に呼び出されるコールバック関数
this.onTick = null;
// タイマー完了時に呼び出されるコールバック関数
this.onComplete = null;
}
// タイマーを開始するメソッド
start() {
// すでに動作中の場合は何もしない
if (this.isRunning) return;
// タイマーを動作中に設定
this.isRunning = true;
// 1秒ごとにtickメソッドを呼び出す
this.intervalId = setInterval(() => this.tick(), 1000);
}
// タイマーを停止するメソッド
stop() {
// 動作中でない場合は何もしない
if (!this.isRunning) return;
// タイマーを停止状態に設定
this.isRunning = false;
// setIntervalをクリア
clearInterval(this.intervalId);
this.intervalId = null;
}
// タイマーをリセットするメソッド
reset() {
// タイマーを停止
this.stop();
// 残り時間を0にリセット
this.remainingSeconds = 0;
// 更新を通知
this.notifyTick();
}
// 1秒ごとに呼び出されるメソッド
tick() {
if (this.remainingSeconds > 0) {
// 残り時間がある場合は1秒減らす
this.remainingSeconds--;
// 更新を通知
this.notifyTick();
} else {
// 残り時間が0になったらタイマーを停止し、完了を通知
this.stop();
this.notifyComplete();
}
}
// タイマーの時間を設定するメソッド
setTime(seconds) {
this.remainingSeconds = seconds;
// 更新を通知
this.notifyTick();
}
// 残り時間を取得するメソッド
getRemainingSeconds() {
return this.remainingSeconds;
}
// タイマーが動作中かどうかを確認するメソッド
isActive() {
return this.isRunning;
}
// 更新を通知するメソッド
notifyTick() {
// onTickが関数として設定されている場合に呼び出す
if (typeof this.onTick === 'function') {
this.onTick(this.remainingSeconds);
}
}
// タイマー完了を通知するメソッド
notifyComplete() {
// onCompleteが関数として設定されている場合に呼び出す
if (typeof this.onComplete === 'function') {
this.onComplete();
}
}
}
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の初期状態を設定するメソッド
initializeUI() {
this.updateTimerDisplay(0);
this.updateButtonStates(false);
}
// タイマーの表示を更新するメソッド
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)}`;
}
// ボタンと入力フィールドの状態を更新するメソッド
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);
}
// エラーメッセージを表示するメソッド
showError(message) {
alert(message);
}
// 入力フィールドの値を取得するメソッド
getInputValues() {
return {
hours: parseInt(this.hoursInput.value, 10) || 0,
minutes: parseInt(this.minutesInput.value, 10) || 0,
seconds: parseInt(this.secondsInput.value, 10) || 0
};
}
// 入力フィールドをリセットするメソッド
resetInputs() {
this.hoursInput.value = '0';
this.minutesInput.value = '0';
this.secondsInput.value = '0';
}
// アラーム音を再生するメソッド
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');
}
}
// アラーム音を停止するメソッド
stopAlarm() {
if (this.alarmAudio) {
this.alarmAudio.pause();
this.alarmAudio.currentTime = 0; // 再生位置を先頭に戻す
this.isAlarmPlaying = false;
this.updateButtonStates(false);
}
}
// アラームが鳴っているかどうかを確認するメソッド
isAlarmActive() {
return this.isAlarmPlaying;
}
// 数字を2桁の文字列に変換するメソッド(例: 1 → "01")
padZero(num) {
return num.toString().padStart(2, '0');
}
}
コピペが終わったら左上の「ファイル」のメニューから「すべて保存」を押してください。
※Ctrlを押しながらKを押して、キーを離してSを押すとショートカットキーですべてのファイルを保存できます。
最後に右下の「Go Live」をクリックするとブラウザが立ち上がりプログラムが実行されます。
拡張機能の「Live Server」をインストールしていると「Go Live」を使用できます。拡張機能の「Live Server」のインストールがまだの方はこちらをご覧ください。
以下のようにブラウザが立ち上がります。
まとめ
アプリの特徴
- モジュール化された設計(Timer、UI、TimerAppクラスに分割)
- レスポンシブデザインにより、様々なデバイスに対応
- アクセシビリティに配慮したHTML構造(aria属性の使用など)
- エラーハンドリング機能(時間未設定時のエラーメッセージ表示)
- ループ再生可能なアラーム機能
- ユーザーフレンドリーなインターフェース(直感的な操作、視覚的フィードバック)
このタイマーアプリケーションは、シンプルさと機能性を兼ね備えた設計が特徴です。HTML、CSS、JavaScriptを使用して構築されており、モダンなウェブ技術を活用しています。
アプリケーションの構造はモジュール化されており、各機能が明確に分離されているため、保守性と拡張性に優れています。ユーザーインターフェースは直感的で使いやすく設計されており、時間の設定からタイマーの操作まで、スムーズに行うことができます。
また、レスポンシブデザインを採用しているため、デスクトップからモバイルまで、様々なデバイスで快適に使用できます。アクセシビリティにも配慮がなされており、スクリーンリーダーなどの支援技術を使用するユーザーにも対応しています。
さらに、エラーハンドリング機能やアラーム機能など、ユーザー体験を向上させる細やかな機能も実装されています。全体として、このアプリケーションは基本的なタイマー機能を提供しつつ、ユーザビリティとアクセシビリティに重点を置いた、実用的で洗練されたウェブアプリケーションとなっています。
コメント