この章では、JavaScriptからHTMLを操作する方法を学びます。
Web制作のJavaScriptでは、DOM操作が中心になります。ハンバーガーメニュー、アコーディオン、タブ、モーダル、フォームのエラー表示などは、どれもDOM操作の組み合わせで作られています。
まずは、要素を取得し、クラスや属性を変更し、必要に応じてテキストやHTMLを変える基本を押さえましょう。
DOMとは
DOMは、ブラウザがHTMLをJavaScriptから扱える形にしたものです。
HTMLファイルには、次のようなコードを書きます。
<button class="js-button" type="button">開く</button>ブラウザはこのHTMLを読み込み、JavaScriptから操作できるオブジェクトとして扱います。
const button = document.querySelector(".js-button");この時、JavaScriptが直接HTMLファイルを書き換えているわけではありません。ブラウザ上に作られたDOMを操作しています。
要素を取得する
DOM操作の最初の一歩は、操作したい要素を取得することです。
よく使うのは、querySelectorとquerySelectorAllです。
querySelector
querySelectorは、条件に合う最初の1つの要素を取得します。
<button class="js-menu-button" type="button">メニュー</button>const menuButton = document.querySelector(".js-menu-button");CSSセレクタと同じように書けるので、クラス、ID、属性などを使って取得できます。
const button = document.querySelector(".js-button");
const header = document.querySelector("#header");
const input = document.querySelector('input[name="email"]');対象要素が見つからない場合、querySelectorはnullを返します。
const modal = document.querySelector(".js-modal");
if (!modal) {
console.log("モーダルはこのページにありません。");
}querySelectorAll
querySelectorAllは、条件に合うすべての要素を取得します。
<button class="js-accordion-button" type="button">質問1</button>
<button class="js-accordion-button" type="button">質問2</button>
<button class="js-accordion-button" type="button">質問3</button>const accordionButtons = document.querySelectorAll(".js-accordion-button");取得した複数要素には、forEachで1つずつ処理できます。
accordionButtons.forEach((button) => {
button.addEventListener("click", () => {
button.classList.toggle("is-active");
});
});getElementById
getElementByIdは、IDを指定して要素を取得します。
<section id="contact"></section>const contactSection = document.getElementById("contact");IDだけを取得するなら使えますが、この教材では基本的にquerySelectorを中心に使います。
理由は、クラス、属性、入れ子の要素なども同じ書き方で取得できるからです。
const contactSection = document.querySelector("#contact");要素の内側から探す
document.querySelectorは、ページ全体から要素を探します。
一方で、特定の要素の中だけから探したい場面もあります。
<div class="accordion js-accordion">
<button class="accordion-button js-accordion-button" type="button">質問</button>
<div class="accordion-panel js-accordion-panel" hidden>回答</div>
</div>const accordion = document.querySelector(".js-accordion");
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");このようにすると、ページ全体ではなく、対象のアコーディオンの中だけから要素を探せます。
複数設置に対応する時に重要です。
const accordions = document.querySelectorAll(".js-accordion");
accordions.forEach((accordion) => {
const button = accordion.querySelector(".js-accordion-button");
const panel = accordion.querySelector(".js-accordion-panel");
if (!button || !panel) {
return;
}
button.addEventListener("click", () => {
panel.hidden = !panel.hidden;
});
});closest
closestは、今の要素から親方向へたどって、条件に合う一番近い要素を取得します。
<article class="card">
<h2>カードタイトル</h2>
<button class="js-card-button" type="button">詳細を見る</button>
</article>const button = document.querySelector(".js-card-button");
const card = button.closest(".card");ボタンがクリックされた時に、そのボタンが属しているカードやアコーディオン全体を取得したい時に便利です。
const buttons = document.querySelectorAll(".js-card-button");
buttons.forEach((button) => {
button.addEventListener("click", () => {
const card = button.closest(".card");
card.classList.add("is-active");
});
});parentElementとchildren
parentElementは、1つ上の親要素を取得します。
childrenは、直下の子要素を取得します。
<ul class="menu">
<li>トップ</li>
<li>サービス</li>
<li>お問い合わせ</li>
</ul>const firstItem = document.querySelector(".menu li");
const menu = firstItem.parentElement;
const items = menu.children;ただし、実務ではHTML構造が変わることもあります。
「1つ上の親」と決め打ちするより、意味のあるクラスを付けてclosestで探した方が壊れにくい場面も多いです。
クラス操作
Web制作では、JavaScriptでクラスを付け外しし、見た目はCSSで制御することが多いです。
この時に使うのがclassListです。
classList.add
classList.addは、クラスを追加します。
menu.classList.add("is-active");メニューを開く、モーダルを表示する、スクロール後のヘッダー状態にする、などで使います。
classList.remove
classList.removeは、クラスを削除します。
menu.classList.remove("is-active");メニューを閉じる、エラー表示を消す、アクティブ状態を外す時に使います。
classList.toggle
classList.toggleは、クラスがなければ追加し、あれば削除します。
menu.classList.toggle("is-active");開閉ボタンのように、同じ操作でオンとオフを切り替える時に便利です。
第2引数を使うと、条件に応じて追加、削除を明示できます。
const isOpen = button.getAttribute("aria-expanded") === "true";
menu.classList.toggle("is-active", !isOpen);この場合、!isOpenがtrueなら追加、falseなら削除されます。
classList.contains
classList.containsは、指定したクラスを持っているか確認します。
const isActive = menu.classList.contains("is-active");
if (isActive) {
console.log("メニューは開いています。");
}今の状態を判定して処理を分ける時に使います。
属性操作
HTML属性を読み書きすることも、DOM操作の重要な役割です。
特に、アクセシビリティに関係するaria-*属性、表示非表示に関係するhidden、フォームに関係するdisabledなどはよく使います。
getAttribute
getAttributeは、属性値を取得します。
<button class="js-menu-button" type="button" aria-expanded="false">メニュー</button>const isOpen = button.getAttribute("aria-expanded") === "true";HTML属性の値は基本的に文字列として取得されます。
そのため、"true"のように文字列で比較しています。
setAttribute
setAttributeは、属性値を設定します。
button.setAttribute("aria-expanded", "true");開閉状態をスクリーンリーダーにも伝えたい時などに使います。
const isOpen = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!isOpen));String(!isOpen)は、真偽値を"true"または"false"の文字列に変換しています。
removeAttribute
removeAttributeは、属性そのものを削除します。
button.removeAttribute("disabled");状態によって属性を消したい時に使います。
hidden
hidden属性は、要素を非表示にするためのHTML属性です。
JavaScriptでは、プロパティとして扱うこともできます。
<div class="js-panel" hidden>パネルの中身</div>panel.hidden = false;開閉UIでは、CSSクラスだけでなくhiddenも組み合わせると、非表示状態がHTMLとしても伝わりやすくなります。
const isOpen = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", String(!isOpen));
panel.hidden = isOpen;
panel.classList.toggle("is-active", !isOpen);disabled
disabledは、フォーム部品やボタンを無効化する属性です。
submitButton.disabled = true;入力内容が不足している時に送信ボタンを無効化したり、送信中に二重クリックを防いだりする時に使います。
const hasValue = input.value !== "";
submitButton.disabled = !hasValue;data属性
data-*属性は、HTMLにJavaScript用の情報を持たせたい時に使います。
<button class="js-tab-button" type="button" data-tab="news">ニュース</button>
<button class="js-tab-button" type="button" data-tab="event">イベント</button>JavaScriptでは、datasetから取得できます。
const tabName = button.dataset.tab;data-tab="news"は、JavaScriptではbutton.dataset.tabとして読めます。
テキストを変更する
要素のテキストを変更する時は、textContentを使います。
<button class="js-button" type="button">開く</button>button.textContent = "閉じる";ボタン文言、エラーメッセージ、カウンター表示などで使います。
errorText.textContent = "メールアドレスを入力してください。";innerHTML
innerHTMLは、HTML文字列として中身を差し替えます。
message.innerHTML = "<strong>送信が完了しました。</strong>";HTMLタグごと追加できるので便利ですが、注意が必要です。
insertAdjacentHTML
insertAdjacentHTMLは、指定した位置にHTMLを追加します。
list.insertAdjacentHTML(
"beforeend",
'<li class="news-list-item">新しいお知らせ</li>'
);第1引数には、追加する位置を指定します。
| 指定 | 位置 |
|---|---|
"beforebegin" | 要素の前 |
"afterbegin" | 要素の中の先頭 |
"beforeend" | 要素の中の末尾 |
"afterend" | 要素の後 |
CMSやAPIから取得した一覧を表示するような場面で使うことがあります。
ただし、この教材ではまずDOM要素を作る方法も扱います。
要素を作成する
JavaScriptで新しい要素を作るには、createElementを使います。
const item = document.createElement("li");
item.textContent = "新しいお知らせ";
item.classList.add("news-list-item");作っただけでは画面に表示されません。
親要素へ追加する必要があります。
list.append(item);appendとprepend
appendは、要素の末尾に追加します。
prependは、要素の先頭に追加します。
list.append(newItem);
list.prepend(importantItem);お知らせ一覧に新しい項目を追加する、エラーメッセージをフォームに追加する、タグを増やす、といった場面で使います。
remove
removeは、要素を削除します。
errorMessage.remove();フォームのエラー表示を消す、閉じるボタンで通知を消す、モーダル内の一時的な要素を消す時に使います。
小さな実装例: エラーメッセージを表示する
ここまでの内容を使って、入力欄が空の時にエラーメッセージを表示する例を見てみましょう。
<form class="contact-form js-contact-form" novalidate>
<label for="name">お名前</label>
<input class="js-name-input" id="name" type="text" aria-describedby="name-error" />
<p class="form-error js-name-error" id="name-error" hidden></p>
<button type="submit">送信する</button>
</form>const form = document.querySelector(".js-contact-form");
const nameInput = document.querySelector(".js-name-input");
const nameError = document.querySelector(".js-name-error");
if (form && nameInput && nameError) {
form.addEventListener("submit", (event) => {
event.preventDefault();
const isEmpty = nameInput.value === "";
nameInput.setAttribute("aria-invalid", String(isEmpty));
nameError.hidden = !isEmpty;
nameError.textContent = isEmpty ? "お名前を入力してください。" : "";
});
}この例では、次のDOM操作を使っています。
querySelectorで要素を取得するsetAttributeでaria-invalidを更新するhiddenでエラーメッセージの表示を切り替えるtextContentでメッセージを入れる
よくある失敗
DOM操作でよくある失敗を整理しておきます。
- 対象要素がないのに処理して
Cannot read properties of nullになる - 複数要素があるのに
querySelectorで最初の1つだけ取得してしまう - ページ全体から要素を探して、別のコンポーネントの要素まで操作してしまう
- CSS用クラスをJavaScriptの取得に使い、デザイン修正で壊れる
innerHTMLにユーザー入力をそのまま入れてしまうaria-expandedやhiddenなどの状態更新を忘れる
DOM操作は「画面が変わればOK」ではありません。
状態が正しく伝わっているか、複数設置でも壊れないか、HTML構造の変更に弱すぎないかも確認しましょう。
この章のまとめ
この章では、Web制作でよく使うDOM操作を学びました。
- DOMは、JavaScriptから操作できる形になったHTML
- 要素の取得には
querySelectorとquerySelectorAllをよく使う - 複数設置では、コンポーネント内から要素を探すと安全
classListで状態クラスを付け外しするgetAttributeやsetAttributeでHTML属性を扱うhidden、disabled、data-*は実務でよく使う- テキスト変更は基本
textContentを使う innerHTMLは便利だが、ユーザー入力を入れる時は注意する
次の章では、ユーザーのクリック、入力、送信、スクロールなどをきっかけに処理を実行するイベント処理を学びます。