コース目次 / 第8章
ミニアプリ
サイトの中に「行きたい場所メモ」を作ります。書いた内容はリロードしても残ります。
コース1でいちばん長い章です。 エラーもいちばん出ます。ぜんぶ理解できなくて大丈夫です。写して、動かして、自分用に変えられれば合格です。時間がかかってもかまわないので、一気にやらず、途中で休んでください。
今日のゴール
サイトに「行きたい場所メモ」のコーナーができます。入力欄に文字を打って「追加」を押すと下に増え、ブラウザを閉じても消えません。いらなくなったら「削除」で消せます。
見るだけのサイトから、使えるアプリになる章です。
ミニ解説その1: 配列
配列 は「順番の付いた箱のリスト」です。メモを何件でも入れておけます。
let memos = ['直島', '尾道'];
memos.push('金沢'); // 末尾に足す → ['直島', '尾道', '金沢']
memos.splice(0, 1); // 0番目から1個抜く → ['尾道', '金沢']数え方は 0から です。1番目ではなく0番目、と覚えてください。プログラミングではずっとこの数え方です。
ミニ解説その2: forEach
forEach(フォーイーチ) は「1個ずつ順番に取り出して、同じことをする」命令です。
memos.forEach((memo, index) => {
// memo … 取り出した中身('直島' など)
// index … それが何番目か(0, 1, 2 …)
});メモが3件なら3回、10件なら10回、中身が実行されます。件数を数える必要はありません。
ミニ解説その3: JSでHTMLを作る
いままでは HTML ファイルに手で書いていましたが、JS からも部品を作れます。
| 書き方 | 意味 |
|---|---|
document.createElement('li') |
新しい <li> を作る |
.textContent = '直島' |
その中の文字を決める |
.appendChild(部品) |
別の部品の中に入れる |
作っただけでは画面に出ません。appendChild でどこかに入れて、はじめて表示されます。
ミニ解説その4: localStorage
localStorage(ローカルストレージ) は、ブラウザの中にある小さなメモ帳です。ここに書いたものは、ブラウザを閉じても残ります。
ただし1つクセがあります。文字しか入れられません。 配列をそのまま入れられないので、変換します。
JSON.stringify(['直島', '尾道']) // 配列 → 文字 '["直島","尾道"]'
JSON.parse('["直島","尾道"]') // 文字 → 配列 ['直島', '尾道']しまうときに stringify、出すときに parse。 この行き帰りだけ覚えれば十分です。
書き写すその1: HTML にメモ欄を足す
まず入れ物を作ります。<input> が入力欄、<button> が追加ボタン、<ul class="memo-list"> がからっぽの置き場です。ここに JS がメモを入れていきます。
<a href="#likes">好きなもの</a> <a href="#hobby">趣味</a>+ <a href="#memo">メモ</a> <a href="#links">リンク</a> <button class="theme-button" type="button">ダーク</button> </section> + <section id="memo">+ <h2>行きたい場所メモ</h2>+ <div class="memo-form">+ <input class="memo-input" type="text" placeholder="行きたい場所" />+ <button class="memo-button" type="button">追加</button>+ </div>+ <ul class="memo-list"></ul>+ </section>+ <section id="links"> <h2>リンク</h2>書き写すその2: CSS で見た目を整える
メディアクエリより前に足します。
} +.memo-form {+display: flex;+gap: 8px;+}+ +.memo-input {+flex: 1;+border: 1px solid #d8d3c8;+border-radius: 8px;+padding: 8px 12px;+font-family: inherit;+font-size: 15px;+}+ +.memo-button {+background-color: #4a6b57;+color: #fbfaf7;+border: none;+border-radius: 8px;+padding: 8px 20px;+font-family: inherit;+font-size: 15px;+cursor: pointer;+}+ +.memo-list {+list-style: none;+margin: 16px 0 0;+padding: 0;+}+ +.memo-list li {+display: flex;+justify-content: space-between;+align-items: center;+gap: 12px;+background-color: #ffffff;+border-radius: 8px;+box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);+padding: 10px 16px;+margin-bottom: 8px;+}+ +.memo-list button {+background-color: transparent;+border: none;+color: #b4703a;+font-family: inherit;+font-size: 13px;+cursor: pointer;+}+ +body.dark .memo-list li {+background-color: #2e3238;+box-shadow: none;+}+ @media (max-width: 600px) { .site-header {保存してリロードすると、入力欄と追加ボタンが出ます。まだ押しても何も起きません。 次でJSを書きます。
書き写すその3: script.js に4つの部品を足す
script.js の、いままで書いたダークモードの下に足していきます。4つの部品に分かれています。1つずつ、順番に写してください。
部品1: 使うものを取ってくる
入力欄・ボタン・置き場の3つと、保存済みのメモを取り出します。
// ここから「行きたい場所メモ」
const memoInput = document.querySelector('.memo-input');
const memoButton = document.querySelector('.memo-button');
const memoList = document.querySelector('.memo-list');
let memos = JSON.parse(localStorage.getItem('haruto-memos') || '[]');|| '[]' は「まだ何も保存されていなければ、からっぽの配列として扱う」という保険です。これがないと、初めて開いた人のページでエラーが出て止まります。
部品2: 保存する係
function saveMemos() {
localStorage.setItem('haruto-memos', JSON.stringify(memos));
}function(ファンクション)は「やることに名前を付けてまとめておく」書き方です。名前を付けておけば、あとから何度でも呼び出せます。
部品3: 画面に並べる係
いちばん長いところです。やっていることは「いったん全部消して、配列の中身を1件ずつ並べ直す」だけです。
function showMemos() {
memoList.textContent = '';
memos.forEach((memo, index) => {
const item = document.createElement('li');
const text = document.createElement('span');
text.textContent = memo;
const deleteButton = document.createElement('button');
deleteButton.textContent = '削除';
deleteButton.addEventListener('click', () => {
memos.splice(index, 1);
saveMemos();
showMemos();
});
item.appendChild(text);
item.appendChild(deleteButton);
memoList.appendChild(item);
});
}削除ボタンの中で showMemos() を呼んでいるのが少し不思議に見えるかもしれません。「配列から消したあと、もう一度並べ直す」という意味です。消すたびに全部並べ直すので、番号のずれを自分で考えなくて済みます。
部品4: 追加ボタンを動かす
memoButton.addEventListener('click', () => {
if (memoInput.value === '') {
return;
}
memos.push(memoInput.value);
memoInput.value = '';
saveMemos();
showMemos();
});
showMemos();if(イフ)は「もし〜なら」です。ここでは「入力欄がからっぽなら return(何もせず終わる)」としています。これがないと、空のメモがいくらでも増えてしまいます。
いちばん下の showMemos(); は、ページを開いた瞬間に1回だけ呼ぶためのものです。これがあるおかげで、前回書いたメモがリロード後も表示されます。
書き終わった script.js の全体
// ヘッダーのボタンを押したら、body に dark クラスを付けたり外したりする
const themeButton = document.querySelector('.theme-button');
themeButton.addEventListener('click', () => {
document.body.classList.toggle('dark');
});
// ここから「行きたい場所メモ」
const memoInput = document.querySelector('.memo-input');
const memoButton = document.querySelector('.memo-button');
const memoList = document.querySelector('.memo-list');
let memos = JSON.parse(localStorage.getItem('haruto-memos') || '[]');
function saveMemos() {
localStorage.setItem('haruto-memos', JSON.stringify(memos));
}
function showMemos() {
memoList.textContent = '';
memos.forEach((memo, index) => {
const item = document.createElement('li');
const text = document.createElement('span');
text.textContent = memo;
const deleteButton = document.createElement('button');
deleteButton.textContent = '削除';
deleteButton.addEventListener('click', () => {
memos.splice(index, 1);
saveMemos();
showMemos();
});
item.appendChild(text);
item.appendChild(deleteButton);
memoList.appendChild(item);
});
}
memoButton.addEventListener('click', () => {
if (memoInput.value === '') {
return;
}
memos.push(memoInput.value);
memoInput.value = '';
saveMemos();
showMemos();
});
showMemos();動かして確かめる
保存してリロードし、次の4つをこの順番で試してください。どこで失敗したかが分かれば、直す場所も絞れます。
- 入力欄に「直島」と入れて 追加 を押す → 下に「直島」が出る
- もう1件足してから リロード する → 2件とも残っている
- 1件目の 削除 を押す → その1件だけ消える
- 何も入力せずに追加 を押す → 何も起きない(増えない)
4つとも通れば完成です。
あなたの番
- メモのテーマを自分のものに変えてください。 見出しの「行きたい場所メモ」と、入力欄の
placeholder="行きたい場所"を書き換えます。「読んだ本メモ」「買うものリスト」「やることリスト」など、自分が使いたいものに。 - 保存する名前も変えておきましょう。
script.jsの'haruto-memos'を、'my-books'のように自分用の名前に。2か所あるので両方変えてください(片方だけだと保存と読み込みがかみ合いません)。 - 変えたら再公開して、スマホからも使ってみてください。
おまけ: おみくじボタンを作る(余裕がある人だけ)
配列と「ランダムに選ぶ」を組み合わせると、おみくじが作れます。HTML に <button class="omikuji">ひく</button> と <p class="omikuji-result"></p> を足して、script.js の最後に次を足します。
const omikujiButton = document.querySelector('.omikuji');
const omikujiResult = document.querySelector('.omikuji-result');
const kuji = ['大吉', '中吉', '小吉', '末吉'];
omikujiButton.addEventListener('click', () => {
const number = Math.floor(Math.random() * kuji.length);
omikujiResult.textContent = kuji[number];
});Math.random() が 0以上1未満の数をでたらめに作り、Math.floor() が小数点以下を切り捨てます。かけ算する相手を kuji.length(配列の件数)にしておくと、件数を増やしてもそのまま動きます。
うまくいかないときは
追加ボタンを押しても何も起きない
開発者ツールの Console を開いて、赤い文字が出ていないか見てください。Cannot read properties of null が出ていたら、HTML の名札と JS の名札の綴りが違います。.memo-input .memo-button .memo-list の3つを、HTML 側と1文字ずつ見比べてください。
リロードすると消えてしまう
2か所を確認します。
script.jsのいちばん下にshowMemos();がありますか。これがないと、保存はされていても画面に出ません。- 追加ボタンの中で
saveMemos();を呼んでいますか。これがないと、画面には出ても保存されていません。
削除すると別のものが消える/全部消える
memos.splice(index, 1); の 1 は「1個だけ抜く」という意味です。ここが違う数字になっていないか確認してください。また splice と slice(cが1つ)は別物です。
もう疲れました
それが普通です。 この章はコース1でいちばん長く、いちばんエラーが出ます。このページの下のZIPをダウンロードして、script.js と index.html と style.css を丸ごと置き換えてしまってください。動いている状態から読み返すほうが、はるかによく分かります。
こうなっていればOK
うまくいかないときは、この章の完成ファイル一式(ZIP)をダウンロードして続きから進めても大丈夫です。
この章はまだ完了していません。