2026年5月12日
冬休み2週間でWebアプリを完成させよう
冬休みに「新年の目標管理アプリ」を作ります。目標を追加・削除でき、達成した目標にチェックを入れると進捗がパーセントで表示されます。localStorage(ローカルストレージ)というブラウザの保存機能を使うので、ページを閉じても目標データが消えません。
この記事では、HTML・CSS・JavaScriptのコードを実際に書きながら、2週間で完成させるスケジュールに沿って進めます。コードはすべてコピーして動かせるので、一緒に手を動かしてみましょう。
夏休みに作れるWebアプリ10選で他のアイデアも見られます。
完成イメージと使う技術
今回作る「目標管理アプリ」の仕様を確認しましょう。
- 入力欄に目標を書いて「追加」ボタンでリストに追加できる
- 各目標にチェックボックスがあり、達成したらチェックを入れる
- ×ボタンで目標を削除できる
- 「達成率〇%」が自動計算されて表示される
localStorageにデータが保存され、ブラウザを閉じても消えない
使う技術は3つです。
- HTML:入力欄・リスト・進捗バーの骨組みを作る
- CSS:カード風のデザインと進捗バーの見た目を整える
- JavaScript:追加・削除・達成チェック・保存のロジックを作る
1つのHTMLファイルと、CSS・JSファイルをそれぞれ1つずつ作ります。特別なソフトは不要で、ブラウザだけで動きます。
Day 1-3: HTML/CSSで見た目を作る
まずはHTMLで画面の構造を作ります。index.htmlというファイルを作り、以下のコードを書いてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2026年の目標リスト</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app-container">
<h1>2026年の目標リスト</h1>
<!-- 目標を追加するフォーム -->
<form id="goal-form">
<input id="goal-input" type="text" placeholder="今年の目標を入力" required>
<button type="submit">追加</button>
</form>
<!-- 進捗バー -->
<div class="progress-wrap">
<div id="progress-bar" class="progress-bar"></div>
</div>
<p id="progress-text">達成率: 0%(0/0)</p>
<!-- 目標のリスト -->
<ul id="goal-list"></ul>
</div>
<script src="script.js"></script>
</body>
</html> ポイント解説:
id="goal-form"は、JavaScriptからフォームを見つけるための名札です。DOM(ドム)操作で使います。required属性で、入力欄が空のまま送信できないようにしています。- 目標のリスト(
<li>要素)は、JavaScriptから動的に追加していくので、最初は空の<ul>だけを用意します。
次にstyle.cssを作り、カード風のデザインを当てます。
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Helvetica Neue", sans-serif;
background-color: #f0f4f8;
display: flex;
justify-content: center;
padding: 40px 16px;
}
.app-container {
background: #fff;
border-radius: 16px;
padding: 32px;
max-width: 480px;
width: 100%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 22px;
margin-bottom: 20px;
}
/* 入力フォーム */
#goal-form {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
#goal-input {
flex: 1;
padding: 10px 12px;
font-size: 15px;
border: 2px solid #ddd;
border-radius: 8px;
}
#goal-form button {
padding: 10px 16px;
font-size: 15px;
background: #007bff;
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
}
/* 進捗バー */
.progress-wrap {
background: #eee;
border-radius: 999px;
height: 12px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-bar {
height: 100%;
width: 0%;
background: #28a745;
transition: width 0.3s;
}
#progress-text {
font-size: 13px;
color: #666;
margin-bottom: 20px;
}
/* 目標リスト */
#goal-list {
list-style: none;
}
#goal-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
#goal-list li.done span {
text-decoration: line-through;
color: #aaa;
}
#goal-list li span {
flex: 1;
}
#goal-list li button {
background: none;
border: none;
color: #dc3545;
font-size: 16px;
cursor: pointer;
}
/* スマホ対応 */
@media (max-width: 400px) {
#goal-form {
flex-direction: column;
}
} .progress-barは今はwidth: 0%ですが、後ほどJavaScriptで達成率に応じて幅を書き換えます。ここまでできたら、ブラウザでHTMLファイルを開いて見た目を確認しておきましょう。
Day 4-7: JavaScriptで追加・削除・達成チェックを作る
script.jsというファイルを作ります。ここがアプリの「頭脳」部分です。
目標データと要素の取得
まず、目標データを入れる配列(はいれつ)と、HTML要素を取得する変数を用意します。
// 目標データを入れる配列(最初は空)
let goals = [];
// HTML要素を取得する(DOM操作)
const goalForm = document.getElementById("goal-form");
const goalInput = document.getElementById("goal-input");
const goalList = document.getElementById("goal-list");
const progressBar = document.getElementById("progress-bar");
const progressText = document.getElementById("progress-text"); 目標を追加する
フォームが送信されたら、入力された文字を新しい目標として配列に追加します。
// フォーム送信時の処理
goalForm.addEventListener("submit", function (e) {
// ページの再読み込みを止める
e.preventDefault();
const text = goalInput.value.trim();
if (text === "") return;
// 新しい目標をオブジェクトとして配列に追加
goals.push({
id: Date.now(), // 現在時刻を一意のIDとして使う
text: text,
done: false,
});
goalInput.value = "";
render();
}); e.preventDefault()は「フォームの初期動作(ページの再読み込み)をしないで」という命令です。Date.now()は現在時刻をミリ秒の数値で返す関数で、目標ごとに重ならないIDとして使えます。
画面に描画する(render関数)
配列の中身をもとに、目標リストと進捗バーを画面に反映する関数を作ります。
// 目標リストと進捗を画面に反映する関数
function render() {
// リストを一度空にする
goalList.innerHTML = "";
goals.forEach(function (goal) {
const li = document.createElement("li");
if (goal.done) li.classList.add("done");
// チェックボックス
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = goal.done;
checkbox.addEventListener("change", function () {
toggleDone(goal.id);
});
// 目標テキスト
const span = document.createElement("span");
span.textContent = goal.text;
// 削除ボタン
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "×";
deleteBtn.addEventListener("click", function () {
deleteGoal(goal.id);
});
li.appendChild(checkbox);
li.appendChild(span);
li.appendChild(deleteBtn);
goalList.appendChild(li);
});
updateProgress();
} この関数は「配列 → 画面表示」の変換をまとめて担当します。データが変わるたびにrender()を呼び直すことで、画面が常に最新の状態になります。
達成チェックと削除の処理
チェックボックスと×ボタンから呼ばれる関数を作ります。目標のIDをもとに、配列から対象を見つけます。
// 達成/未達成を切り替える
function toggleDone(id) {
// find()で該当するidの目標を探す
const goal = goals.find(function (g) {
return g.id === id;
});
goal.done = !goal.done;
render();
}
// 目標を削除する
function deleteGoal(id) {
// filter()でidが一致しないものだけ残す(=該当を除外)
goals = goals.filter(function (g) {
return g.id !== id;
});
render();
} find()は配列から条件に合う1つの要素を探すメソッド、filter()は条件に合う要素だけを集めた新しい配列を作るメソッドです。削除では「消したいもの以外を残す」という考え方を使っています。
進捗を計算する
達成数÷全体数で達成率を計算し、進捗バーの幅とテキストを更新します。
// 進捗(達成率)を計算して表示する
function updateProgress() {
const total = goals.length;
const doneCount = goals.filter(function (g) {
return g.done;
}).length;
// 目標が0件のときは0%にする(0÷0を避ける)
const percent = total === 0 ? 0 : Math.round((doneCount / total) * 100);
progressBar.style.width = percent + "%";
progressText.textContent =
"達成率: " + percent + "%(" + doneCount + "/" + total + ")";
} たとえば目標が4件で2件達成していれば、doneCount / total = 2 / 4 = 0.5、Math.round(0.5 * 100)で50となり「達成率: 50%(2/4)」と表示されます。目標が0件のときに0で割ってしまう(NaNになる)のを防ぐため、total === 0の分岐を入れているのがポイントです。
最後に、最初の画面表示のためにrender()を1回呼んでおきます。
// 最初の描画
render(); ここまでで、追加・達成チェック・削除・進捗表示がすべて動くようになりました。ブラウザで動作を確認してみましょう。
Day 8-10: localStorageでデータを保存する
今のままでは、ページを再読み込みすると目標データが消えてしまいます。localStorageを使って、ブラウザにデータを保存しましょう。
render()の中に保存処理を追加し、ページ読み込み時に復元する処理を足します。
// 目標データを保存する
function save() {
// 配列を文字列(JSON)に変換して保存する
localStorage.setItem("goals", JSON.stringify(goals));
}
// 保存されたデータを読み込む
function load() {
const saved = localStorage.getItem("goals");
// 保存データがあれば文字列を配列に戻す。なければ空配列
goals = saved ? JSON.parse(saved) : [];
} localStorageは文字列しか保存できないため、JSON.stringify()で配列を文字列に変換してから保存し、読み込むときはJSON.parse()で配列に戻します。
render()関数の最後にsave()の呼び出しを1行追加します。
function render() {
goalList.innerHTML = "";
goals.forEach(function (goal) {
// (中身は変更なし。Day 4-7で作った処理のまま)
});
updateProgress();
save(); // ← ここを追加:描画するたびに保存する
} そして、ファイルの一番下にあったrender();を、読み込み処理とセットに書き換えます。
// ページ読み込み時:保存データを復元してから描画する
load();
render(); これで、目標を追加した後にページを閉じても、次に開いたときに同じ目標リストが表示されます。ブラウザの開発者ツールで「Application → Local Storage」を見ると、goalsというキーにデータが保存されているのが確認できます。
Day 11-14: テスト・改善・公開
完成が近づいたら、実際に使ってみてバグを見つけ、仕上げをしましょう。
チェックすべき動作パターン
- 入力欄が空のまま「追加」を押すとどうなるか(
requiredとtrim() === ""のチェックで防げているか) - すべての目標を削除したとき、達成率が「0%(0/0)」に戻るか(0除算対策が効いているか)
- チェックを入れたり外したりを繰り返して、達成率が正しく増減するか
- ページを再読み込みしても目標が消えないか(localStorageの保存・復元)
- スマホの画面幅で表示が崩れないか
友達や家族に使ってもらい、フィードバックをもらうのもおすすめです。「目標が多いと見づらい」「達成した項目を上に固定したい」など、実際に使った人の声から改善アイデアが見つかります。
動作が安定したら、GitHub Pagesで公開しましょう。公開すればURLを共有するだけで、誰でもアプリにアクセスできます。
カスタマイズアイデア
基本の目標管理アプリが完成したら、自分なりに機能を追加してみましょう。
1. カテゴリ分けを追加する
目標データにcategoryというプロパティ(例:"勉強"、"運動"、"趣味")を追加し、カテゴリごとに色分けして表示します。goals.push()する際にcategoryも一緒に保存する形に拡張すれば実現できます。
2. 期限設定を追加する
目標データにdeadline(期限の日付)を追加し、期限が近い目標を上に並べ替えて表示します。配列のsort()メソッドを使うと並べ替えができます。
3. ダークモード切り替えを追加する
ボタンをクリックするとbody要素にdark-modeクラスをつけ外しし、CSS側で色を切り替える仕組みです。classList.toggle("dark-mode")の1行で実装できます。
独学ロードマップで次に学ぶべきことを確認しましょう。
まとめ
- ✅ Day 1-3: HTML/CSSで入力欄・リスト・進捗バーの見た目を作った
- ✅ Day 4-7: JSで追加・削除・達成チェック・進捗計算を実装した
- ✅ Day 8-10: localStorageでデータの保存と復元を実装した
- ✅ Day 11-14: 動作確認とフィードバックを経てGitHub Pagesで公開する
配列の操作(push・find・filter)、DOM操作、イベント処理、localStorageによるデータ保存。この目標管理アプリには、Webアプリ作りに欠かせない基本テクニックが詰まっています。
冬休みの2週間で1つのアプリを完成させた経験は、次のアプリ制作でも必ず活きます。ぜひ自分だけの目標リストを育てていってください。