はじめに ✨
「まずは動くアプリを作ってみたい」。
そんなときに最適なのが、TODOアプリです。
とくに Web上で動くアプリが始めやすいです。
TODOアプリには小さいながら
「アプリの基本サイクル」が入っています。
今回の目標は、HTML / CSS / JavaScript
だけでシンプルなTODOアプリを作ることです。
言語:HTML / CSS / JavaScript
データの保存先:LocalStorage
用語の説明 📚
Webサイト全体を“家”とたとえて考える
開発するものがWebアプリである為、
Webサイト全体を“家”とたとえて考えていきましょう。
- 🏠 HTML は家の部屋や家具そのもの。
- 🎨 CSS は家の部屋や家具の見た目を整えるもの。
- ⚡ JavaScript は家の部屋や家具の動作を与えるもの。
- 🗄️ LocalStorage は家の共用の小物入れ。
HTMLの役割(部屋と家具) 🏠
HTMLは家の部屋や家具そのもの。
- 部屋(リビングや寝室のようにページを区切る)
- 机(入力欄)
- 椅子(追加ボタン)
- 棚(タスク一覧を置く場所)
👉 家の形はあるけれど、まだ見た目や動きはありません。
CSSの役割(部屋と家具の見た目) 🎨
CSSは家の部屋や家具の見た目を整えるもの。
- 壁紙や床(背景や色合い)
- 家具のデザイン(ボタンやリストのスタイル)
- 家具の配置(レイアウト)
👉 同じ部屋と家具でも、CSSで「明るいリビング」や「落ち着いた書斎」に変えられます。
JavaScriptの役割(部屋と家具の動作) ⚡
JavaScriptは家の部屋や家具の動作を与えるもの。
- 椅子を引けば座れる(ボタンを押すとタスク追加)
- 照明をつければ部屋が明るくなる(チェックで完了)
- 棚から物を片付ける(削除でタスクを消す)
👉 部屋と家具があるだけでは生活できません。
LocalStorageの役割(共用の小物入れ)🗄️
🗄️ LocalStorage は家の共用の小物入れ。
・🏠 家 = Webサイト
・🗄️ 共用の小物入れ = LocalStorage
・家(=Webサイト)ごとにひとつ
・どの部屋(ページ)からも同じ小物入れを使える
・別の家(別サイト)では中身は共有されない
👉 LocalStorage = その家専用の「共用小物入れ」
アプリの機能🎯
完成品のイメージを考えていきましょう。
アプリの機能が次の通りです。
・📝 タスクを入力して追加
・ ✔️ チェックで打ち消し線
・🗑️ 削除ボタンで消せる
アプリの仕組み 🔧
アプリの仕組みは次の4つです。
・仕組み1:タスクを追加する ✏️
・仕組み2:タスクを完了にする ✔️
・仕組み3:タスクを削除する 🗑️
・仕組み4:ページを再読み込みする 🔄
仕組み1:タスクを追加する ✏️
1.ユーザーが入力欄に「牛乳を買う」と
入力してEnterキー or 追加ボタンを押す
- JavaScriptの add() 関数が呼ばれる
- 入力値をトリムして空でないか確認
- todos 配列に { text: “牛乳を買う”, done: false } を追加
- save() を呼び出して LocalStorage に保存
- render() を呼び出して画面を再描画
→ 新しいリスト項目が表示される
👉 外から見ると「入力したらリストに出てくる」。
👉 内部では「配列に追加 → LocalStorageに保存
→ HTMLを再生成」。
仕組み2:タスクを完了にする ✔️
- ユーザーがリストのチェックボックスをクリック
- その項目の done フラグが false → true に切り替わる
- save() が呼ばれ、変更後の配列を LocalStorage に再保存
- チェックが入った項目のタイトルに done クラスがつき、打ち消し線スタイルが反映される
👉 外から見ると「チェックを入れたら線が引かれる」。
👉 内部では「配列を更新 → LocalStorageに保存 → クラス切替で見た目を変更」。
仕組み3:タスクを削除する 🗑️
- ユーザーが削除ボタンをクリック
- todos.splice(i, 1) で配列からその項目を削除
- save() を呼び出して LocalStorage を更新
- render() を呼び出して、最新の配列をもとに画面を再描画
👉 外から見ると「削除を押すと項目が消える」。
👉 内部では「配列から削除 → LocalStorageを更新 → 画面を再生成」。
仕組み4:ページを再読み込みする 🔄
- ユーザーがページをリロード or ブラウザを閉じて再度開く
- 最初に
let todos = JSON.parse(localStorage.getItem(“todos”) || “[]”);
が実行される - LocalStorage から配列データを復元
- render() が呼ばれ、保存していた内容が画面に表示される
👉 外から見ると「閉じてもリストが残っている」。
👉 内部では「LocalStorageから復元 → 配列に展開 → HTMLに反映」。
コード 💻
1ファイルで簡単管理
本来は HTML / CSS / JavaScript を
別ファイルに分けるのが正しいですが、
手軽さを優先して
1ファイルにまとめています。
📂 ファイル名は index.html にしてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>TODOアプリ</title>
<style>
:root{
--bg:#f6f8ff; --panel:#fff; --line:#e6e9f3;
--text:#222; --muted:#667; --accent:#4a90e2; --danger:#e74c3c;
}
*{box-sizing:border-box}
body{
margin:0; font:16px/1.6 system-ui, sans-serif; color:var(--text);
background:linear-gradient(180deg,#fff,#f6f8ff 60%, #eef2fb);
min-height:100dvh; display:grid; place-items:start center;
}
.app{ width:min(760px,94%); margin:48px auto; }
.card{
background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:18px;
box-shadow:0 6px 14px rgba(0,0,0,.06);
}
h1{ margin:0 0 12px; font-size:22px }
.badge{ margin-left:.6rem; font-size:12px; color:var(--muted);
border:1px solid var(--line); padding:2px 8px; border-radius:999px; }
.row{ display:flex; gap:10px; margin:8px 0 10px }
.input{ flex:1; padding:10px 12px; border:1px solid var(--line); border-radius:8px; outline:none; }
.input:focus{ border-color:var(--accent); box-shadow:0 0 0 2px rgba(74,144,226,.25) }
.btn{ border:0; cursor:pointer; padding:10px 14px; border-radius:8px; font-weight:600; background:var(--accent); color:#fff; }
ul{ list-style:none; margin:0; padding:0 }
li{ display:flex; align-items:center; gap:10px; background:#fafbff; border:1px solid var(--line);
padding:10px; border-radius:8px; margin:6px 0; }
li input[type="checkbox"]{ width:18px; height:18px }
.title.done{ text-decoration:line-through; color:var(--muted) }
.spacer{ flex:1 }
.del{ border:0; cursor:pointer; padding:6px 10px; border-radius:6px; background:var(--danger); color:#fff; font-weight:600; }
.hint{ color:var(--muted); font-size:12px; margin-top:6px; text-align:right }
@media (max-width:560px){ .row{ flex-direction:column } }
</style>
</head>
<body>
<div class="app">
<div class="card">
<h1>TODOアプリ <span class="badge">Vanilla JS</span></h1>
<div class="row">
<input id="todoInput" class="input" type="text" placeholder="やることを入力" />
<button id="addBtn" class="btn">追加</button>
</div>
<ul id="todoList"></ul>
<div class="hint">Enterで追加 / チェックで完了 / 削除で削除(保存先: LocalStorage)</div>
</div>
</div>
<script>
const input = document.getElementById("todoInput");
const addBtn = document.getElementById("addBtn");
const todoList = document.getElementById("todoList");
let todos = JSON.parse(localStorage.getItem("todos") || "[]");
function save(){ localStorage.setItem("todos", JSON.stringify(todos)); }
function render(){
todoList.innerHTML = "";
todos.forEach((t, i) => {
const li = document.createElement("li");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.checked = t.done;
cb.onchange = () => {
todos[i].done = cb.checked;
save();
title.classList.toggle("done", cb.checked);
};
const title = document.createElement("span");
title.className = "title" + (t.done ? " done" : "");
title.textContent = t.text;
const spacer = document.createElement("span");
spacer.className = "spacer";
const del = document.createElement("button");
del.className = "del";
del.textContent = "削除";
del.onclick = () => {
todos.splice(i, 1);
save();
render();
};
li.appendChild(cb);
li.appendChild(title);
li.appendChild(spacer);
li.appendChild(del);
todoList.appendChild(li);
});
}
function add(){
const text = input.value.trim();
if(!text) return;
todos.push({ text, done:false });
save();
render();
input.value = "";
input.focus();
}
addBtn.addEventListener("click", add);
input.addEventListener("keydown", e => { if(e.key === "Enter") add(); });
render();
</script>
</body>
</html>
動作確認 🖱️
ブラウザで開いてから次の4つを動作確認します。
- 仕組み1:タスクを追加する ✏️
- 仕組み2:タスクを完了にする ✔️
- 仕組み3:タスクを削除する 🗑️
- 仕組み4:ページを再読み込みする 🔄
コメント