JavaScriptで時間管理アプリを作る【STEP1〜10】
HTML/CSSは最小限、JavaScriptだけで機能を積み上げていく入門シリーズです。
各STEPで「使うJS機能」「考え方」「シンプルな例」→「アプリへの応用」の順に解説します。
完成図
今回はJSの勉強なので、HTML、CSSはここからコピペする。
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>時間管理アプリ</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="container">
<h1>今日の時間管理</h1>
<div class="form">
<select id="start"></select>
<select id="end"></select>
<input id="title" type="text" placeholder="内容を書く" />
<div id="colorPicker" class="colors"></div>
<button id="addBtn">追加</button>
</div>
<div class="tabs">
<button data-view="list" class="active">リスト</button>
<button data-view="table">円グラフ</button>
<button data-view="graph">割合</button>
</div>
<div id="view"></div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS
body {
font-family: system-ui, sans-serif;
background: #f3f4f6;
}
.container {
width: 420px;
margin: 40px auto;
background: #fff;
padding: 20px;
border-radius: 12px;
}
h1 {
text-align: center;
}
.form {
display: flex;
flex-direction: column;
gap: 10px;
}
select, input {
padding: 8px;
font-size: 14px;
}
button {
padding: 10px;
border: none;
background: #2563eb;
color: white;
border-radius: 6px;
cursor: pointer;
}
.colors {
display: flex;
gap: 6px;
}
.color {
width: 24px;
height: 24px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
}
.color.selected {
border-color: black;
}
.tabs {
display: flex;
gap: 6px;
margin: 16px 0;
}
.tabs button {
flex: 1;
background: #e5e7eb;
color: black;
}
.tabs .active {
background: #2563eb;
color: white;
}
.item {
padding: 8px;
border-radius: 6px;
margin-bottom: 6px;
font-size: 14px;
}
.pie {
width: 220px;
height: 220px;
border-radius: 50%;
margin: 20px auto;
}
.bar {
height: 24px;
border-radius: 6px;
margin-bottom: 8px;
color: white;
text-align: right;
padding-right: 6px;
font-size: 12px;
}
STEP1|選択肢を JS で生成する
ゴール
開始時間・終了時間の <select> に、00:00〜23:30 を30分単位で表示させる。
HTMLは編集せず、JavaScriptだけで選択肢を作る。
使う JS 機能
-
for文(繰り返し) - 配列(
["00", "30"]) - 文字列操作(
padStart) - DOM 操作(
innerHTML) - テンプレート文字列(
`${}`)
考え方
| やること | 方法 |
|---|---|
| 時間を数で扱う | 0〜23 を for 文で回す |
| 30分単位にする | 分は "00" "30" の2パターン |
| 表示用文字列を作る |
"09:30" のような形に整形 |
<option> を追加する |
HTML に直接書かない |
シンプルな例
数字の選択肢を JS で作る場合も考え方は同じです。
<!-- HTML -->
<select id="number"></select>
const select = document.getElementById("number");
for (let i = 1; i <= 5; i++) {
select.innerHTML += `<option>${i}</option>`;
}
「繰り返して option を追加する」——時間生成も全く同じ考え方です。
STEP2|ボタンを押して値を取得する
ゴール
「追加」ボタンを押したときに 開始時間・終了時間・内容 を JavaScript で取得できる状態にする。
画面表示はまだ変えない(console.log で OK)。
使う JS 機能
- DOM 取得(
getElementById) - イベント処理(
onclick) - 値の取得(
.value) console.log
考え方
- HTML の入力欄は JS から取得できる
- ボタンが押された瞬間に処理を動かす
- 入力値は
.valueで取り出す - まずは console に出して確認する
シンプルな例
<!-- HTML -->
<input id="name" type="text" />
<button id="btn">表示</button>
const input = document.getElementById("name");
const button = document.getElementById("btn");
button.onclick = () => {
console.log(input.value);
};
STEP3|データを配列に保存する
ゴール
STEP2 で取得した 開始時間・終了時間・内容 を1つのデータとしてまとめて保存する。
保存先は配列(logs)。まだ画面表示はしない(console.log で確認)。
使う JS 機能
- 配列(
[]) - オブジェクト(
{}) push()console.log
考え方
- 1回の入力 = 1つのデータ
- 複数の値はオブジェクトにまとめる
- データは配列に貯めていく
- まずは「ちゃんと入ったか」を確認する
シンプルな例
const names = [];
function addName(name) {
names.push({ name: name });
console.log(names);
}
addName("太郎");
addName("花子");
// [{ name: "太郎" }, { name: "花子" }]
時間管理アプリに当てはめると
logs.push({
start: "09:00",
end: "10:30",
title: "仕事"
});
STEP4|データを画面に一覧表示する
ゴール
STEP3 で配列に保存したデータを使って画面に一覧表示する。
データが追加されるたびに表示を更新する。
使う JS 機能
- 配列のループ(
forEach) - DOM 操作(
createElement/appendChild/innerHTML) - テンプレート文字列(
`${}`)
考え方
- 画面表示は「配列の中身」を元に作る
- 表示する前に一度表示エリアを空にする
- 配列を1つずつ回して HTML 要素を作り、画面に追加する
- データが正、画面は結果という考え方
シンプルな例
<!-- HTML -->
<ul id="list"></ul>
const list = document.getElementById("list");
const fruits = ["りんご", "みかん", "バナナ"];
list.innerHTML = "";
fruits.forEach(fruit => {
const li = document.createElement("li");
li.textContent = fruit;
list.appendChild(li);
});
時間管理アプリに当てはめると
view.innerHTML = "";
logs.forEach(log => {
const div = document.createElement("div");
div.textContent = `${log.start} - ${log.end}|${log.title}`;
view.appendChild(div);
});
STEP5|開始時間の早い順に並べる
ゴール
一覧表示を**開始時間の早い順(時間順)**に並べ替える。
データの保存順ではなく、時間として正しい順番で表示する。
使う JS 機能
- 配列の
sort() - 数値変換
- 関数
- 文字列操作(
split)
考え方
- 時間(
"09:30")は そのままでは比較できない - 時間を数値(分)に変換すると比較しやすい(例:
"09:30"→570) - 並べ替えは表示するときに行う
- 元の配列は壊さない
シンプルな例
const numbers = [5, 1, 10, 3];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 3, 5, 10]
a - b→ 昇順 /b - a→ 降順
時間管理アプリに当てはめると
function toMinutes(time) {
const [h, m] = time.split(":").map(Number);
return h * 60 + m;
}
// [...logs] で元配列を壊さずコピーしてから sort
const sortedLogs = [...logs].sort((a, b) => {
return toMinutes(a.start) - toMinutes(b.start);
});
sortedLogs.forEach(log => {
// 表示処理
});
[...]を使う理由 → 元のlogsを壊さないため
STEP6|時間の重複をチェックする
ゴール
新しく追加しようとしている時間がすでに登録されている時間と被っていないかをチェックする。
被っていた場合はエラーを出し、配列に追加しない。
使う JS 機能
- 配列の
some() - 条件式(
if) - 関数
- 早期リターン(
return)
考え方
時間の重なりは以下の条件で起きる。
新しい開始 < 既存の終了 かつ 新しい終了 > 既存の開始
- 配列の中に1つでも条件を満たすものがあればアウト
- チェック用の処理は関数に切り出す
- 被っていたらそれ以上処理を進めない(早期リターン)
シンプルな例
const numbers = [3, 5, 8];
const hasOverlap = numbers.some(n => n === 5);
console.log(hasOverlap); // true
some()→ 条件を満たす要素が 1つでもあればtrue、全部falseならfalse
時間管理アプリに当てはめると
function isOverlap(start, end) {
return logs.some(log => {
return (
toMinutes(start) < toMinutes(log.end) &&
toMinutes(end) > toMinutes(log.start)
);
});
}
if (isOverlap(start, end)) {
alert("時間が他の予定と被っています");
return; // 早期リターンで処理を止める
}
STEP7|色を選択して状態を管理する
ゴール
色パレットをクリックすると「選択中の色」を保存できるようにする。
選ばれた色が見た目でわかる(選択中表示)+データとして保存できる。
使う JS 機能
- DOM 操作(
querySelectorAll/classList) - イベント処理(クリック)
- 変数による状態管理
forEach
考え方
- 色も入力値のひとつとして扱う
- クリックされた色を変数に保存する(例:
selectedColor) - 選択中かどうかは class の ON / OFF で表現する
- 「見た目の状態」と「JS の状態」を一致させる
シンプルな例
<!-- HTML -->
<button class="btn">赤</button>
<button class="btn">青</button>
<button class="btn">緑</button>
/* CSS */
.selected {
border: 2px solid black;
}
const buttons = document.querySelectorAll(".btn");
let selected = "";
buttons.forEach(btn => {
btn.onclick = () => {
// 全部の選択状態を外す
buttons.forEach(b => b.classList.remove("selected"));
// クリックしたものを選択
btn.classList.add("selected");
selected = btn.textContent;
console.log(selected);
};
});
「クリック → 状態を保存 → 見た目を更新」という流れが UI 管理の基本です。
STEP8|表示モードを切り替える
ゴール
「リスト / 円グラフ / 割合」の表示モードを切り替えられるようにする。
ボタン(タブ)をクリックすると表示内容が切り替わり、選択中のタブが見た目で分かる。
使う JS 機能
- DOM 取得(
querySelectorAll) - イベント処理(クリック)
- 変数による状態管理
datasetclassList
考え方
- 今どの表示モードかを変数で管理する(例:
currentView) - タブをクリックしたら → 状態を更新 → 表示を作り直す
- 表示内容は
ifやswitchで切り替える - 表示は毎回「データから作る」
シンプルな例
<!-- HTML -->
<button data-view="a">A</button>
<button data-view="b">B</button>
<div id="content"></div>
const buttons = document.querySelectorAll("button");
const content = document.getElementById("content");
let current = "a";
buttons.forEach(btn => {
btn.onclick = () => {
current = btn.dataset.view;
render();
};
});
function render() {
if (current === "a") content.textContent = "Aの表示";
if (current === "b") content.textContent = "Bの表示";
}
datasetを使うと HTML と JS の対応が分かりやすくなります。
STEP9|円グラフで可視化する
ゴール
登録された時間データを使って 1日(24時間)を円グラフで可視化する。
各予定の時間割合を色付きの円グラフとして表示する。
使う JS 機能
- 配列の
map - 数値計算(割合)
- 変数の累積(角度)
- 文字列結合
- DOM 操作(
style.background)
考え方
| 変換ステップ | 内容 |
|---|---|
| 1日 = 1440分 | 各予定の時間(分)を全体に対する割合に変換 |
| 割合 → 角度 | 360度に変換する |
| 角度 → CSS |
conic-gradient に渡す |
| 累積 | 「どこからどこまでの角度か」を計算する |
シンプルな例
<!-- HTML -->
<div id="pie" style="width:200px;height:200px;border-radius:50%;"></div>
const pie = document.getElementById("pie");
const data = [
{ color: "red", percent: 25 },
{ color: "blue", percent: 50 },
{ color: "green", percent: 25 }
];
let current = 0;
const gradient = data.map(d => {
const deg = (d.percent / 100) * 360;
const str = `${d.color} ${current}deg ${current + deg}deg`;
current += deg;
return str;
}).join(",");
pie.style.background = `conic-gradient(${gradient})`;
円グラフは JS で計算、CSS で描画。角度を累積していくのがポイントです。
STEP10|色ごとに集計して割合を出す
ゴール
登録されたデータを色ごとに集計する。
「この色が何%使われているか」を表示する。円グラフとは別の視点で使い方の偏りが分かる。
使う JS 機能
- 配列の
forEach - オブジェクト(
{}) - 集計処理
reduce- 数値計算(割合)
考え方
- 同じ色の時間は合算する
- 集計結果は
{ 色: 合計時間 }の形で持つ - 全体の時間を基準に**%を計算する**
- 集計結果をループして表示する
シンプルな例
const items = ["red", "blue", "red", "green", "blue"];
const count = {};
items.forEach(item => {
count[item] = (count[item] || 0) + 1;
});
console.log(count);
// { red: 2, blue: 2, green: 1 }
(値 || 0)は初期値がないときに0を使う便利なテクニックです。
時間管理アプリに当てはめると
const byColor = {};
logs.forEach(log => {
byColor[log.color] = (byColor[log.color] || 0) + log.minutes;
});
const total = logs.reduce((sum, log) => sum + log.minutes, 0);
Object.entries(byColor).forEach(([color, minutes]) => {
const percent = Math.round((minutes / total) * 100);
// 表示処理
});
まとめ
| STEP | やること | 主なJS機能 |
|---|---|---|
| 1 | 選択肢を動的生成 |
for / innerHTML
|
| 2 | ボタンで値を取得 |
onclick / .value
|
| 3 | データを配列に保存 |
push / オブジェクト |
| 4 | 一覧表示 |
forEach / createElement
|
| 5 | 時間順に並べ替え |
sort / 数値変換 |
| 6 | 重複チェック |
some / 早期リターン |
| 7 | 色の状態管理 |
classList / querySelectorAll
|
| 8 | 表示モード切替 |
dataset / 状態変数 |
| 9 | 円グラフ描画 |
map / conic-gradient
|
| 10 | 色ごとに集計 |
forEach / reduce
|
「入力 → 保存 → 表示 → 並び替え → バリデーション → 可視化」という実務 JS の流れが一通り身につく構成になっています。


