プログラミング初心者向け1ファイルで作るTODOアプリ

プログラミング

はじめに ✨

「まずは動くアプリを作ってみたい」。

そんなときに最適なのが、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 追加ボタンを押す

  1. JavaScriptの add() 関数が呼ばれる
  2. 入力値をトリムして空でないか確認
  3. todos 配列に { text: “牛乳を買う”, done: false } を追加
  4. save() を呼び出して LocalStorage に保存
  5. render() を呼び出して画面を再描画
       → 新しいリスト項目が表示される

👉 外から見ると「入力したらリストに出てくる」。
👉 内部では「配列に追加 → LocalStorageに保存
   → HTMLを再生成」。

仕組み2:タスクを完了にする ✔️

  1. ユーザーがリストのチェックボックスをクリック
  2. その項目の done フラグが false → true に切り替わる
  3. save() が呼ばれ、変更後の配列を LocalStorage に再保存
  4. チェックが入った項目のタイトルに done クラスがつき、打ち消し線スタイルが反映される

👉 外から見ると「チェックを入れたら線が引かれる」。
👉 内部では「配列を更新 → LocalStorageに保存 → クラス切替で見た目を変更」。

仕組み3:タスクを削除する 🗑️

  1. ユーザーが削除ボタンをクリック
  2. todos.splice(i, 1) で配列からその項目を削除
  3. save() を呼び出して LocalStorage を更新
  4. render() を呼び出して、最新の配列をもとに画面を再描画

👉 外から見ると「削除を押すと項目が消える」。
👉 内部では「配列から削除 → LocalStorageを更新 → 画面を再生成」。

仕組み4:ページを再読み込みする 🔄

  1. ユーザーがページをリロード or ブラウザを閉じて再度開く
  2. 最初に
     let todos = JSON.parse(localStorage.getItem(“todos”) || “[]”);
    が実行される
  3. LocalStorage から配列データを復元
  4. 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:ページを再読み込みする 🔄

コメント

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