0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

シンプルなJs練習

0
Last updated at Posted at 2026-03-02

JavaScriptで時間管理アプリを作る【STEP1〜10】

HTML/CSSは最小限、JavaScriptだけで機能を積み上げていく入門シリーズです。
各STEPで「使うJS機能」「考え方」「シンプルな例」→「アプリへの応用」の順に解説します。

完成図

スクリーンショット 2026-03-02 20.27.45.png

スクリーンショット 2026-03-02 20.28.35.png

スクリーンショット 2026-03-02 20.28.43.png


今回は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
  • イベント処理(クリック)
  • 変数による状態管理
  • dataset
  • classList

考え方

  • 今どの表示モードかを変数で管理する(例:currentView
  • タブをクリックしたら → 状態を更新 → 表示を作り直す
  • 表示内容は ifswitch で切り替える
  • 表示は毎回「データから作る」

シンプルな例

<!-- 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 の流れが一通り身につく構成になっています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?