04. DOM操作の基本

この章では、JavaScriptからHTMLを操作する方法を学びます。

Web制作のJavaScriptでは、DOM操作が中心になります。ハンバーガーメニュー、アコーディオン、タブ、モーダル、フォームのエラー表示などは、どれもDOM操作の組み合わせで作られています。

まずは、要素を取得し、クラスや属性を変更し、必要に応じてテキストやHTMLを変える基本を押さえましょう。

DOMとは

DOMは、ブラウザがHTMLをJavaScriptから扱える形にしたものです。

HTMLファイルには、次のようなコードを書きます。

HTML
<button class="js-button" type="button">開く</button>

ブラウザはこのHTMLを読み込み、JavaScriptから操作できるオブジェクトとして扱います。

JavaScript
const button = document.querySelector(".js-button");

この時、JavaScriptが直接HTMLファイルを書き換えているわけではありません。ブラウザ上に作られたDOMを操作しています。

要素を取得する

DOM操作の最初の一歩は、操作したい要素を取得することです。

よく使うのは、querySelectorquerySelectorAllです。

querySelector

querySelectorは、条件に合う最初の1つの要素を取得します。

HTML
<button class="js-menu-button" type="button">メニュー</button>
JavaScript
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"]');

対象要素が見つからない場合、querySelectornullを返します。

要素がない可能性がある
const modal = document.querySelector(".js-modal");

if (!modal) {
    console.log("モーダルはこのページにありません。");
}

querySelectorAll

querySelectorAllは、条件に合うすべての要素を取得します。

HTML
<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>
JavaScript
const accordionButtons = document.querySelectorAll(".js-accordion-button");

取得した複数要素には、forEachで1つずつ処理できます。

複数要素に処理する
accordionButtons.forEach((button) => {
    button.addEventListener("click", () => {
        button.classList.toggle("is-active");
    });
});

getElementById

getElementByIdは、IDを指定して要素を取得します。

HTML
<section id="contact"></section>
JavaScript
const contactSection = document.getElementById("contact");

IDだけを取得するなら使えますが、この教材では基本的にquerySelectorを中心に使います。

理由は、クラス、属性、入れ子の要素なども同じ書き方で取得できるからです。

querySelectorでIDを取得する
const contactSection = document.querySelector("#contact");

要素の内側から探す

document.querySelectorは、ページ全体から要素を探します。

一方で、特定の要素の中だけから探したい場面もあります。

HTML
<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は、今の要素から親方向へたどって、条件に合う一番近い要素を取得します。

HTML
<article class="card">
    <h2>カードタイトル</h2>
    <button class="js-card-button" type="button">詳細を見る</button>
</article>
closest
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は、直下の子要素を取得します。

HTML
<ul class="menu">
    <li>トップ</li>
    <li>サービス</li>
    <li>お問い合わせ</li>
</ul>
parentElementとchildren
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引数を使うと、条件に応じて追加、削除を明示できます。

条件付きtoggle
const isOpen = button.getAttribute("aria-expanded") === "true";

menu.classList.toggle("is-active", !isOpen);

この場合、!isOpentrueなら追加、falseなら削除されます。

classList.contains

classList.containsは、指定したクラスを持っているか確認します。

クラスを持っているか確認する
const isActive = menu.classList.contains("is-active");

if (isActive) {
    console.log("メニューは開いています。");
}

今の状態を判定して処理を分ける時に使います。

属性操作

HTML属性を読み書きすることも、DOM操作の重要な役割です。

特に、アクセシビリティに関係するaria-*属性、表示非表示に関係するhidden、フォームに関係するdisabledなどはよく使います。

getAttribute

getAttributeは、属性値を取得します。

HTML
<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では、プロパティとして扱うこともできます。

HTML
<div class="js-panel" hidden>パネルの中身</div>
hiddenを切り替える
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用の情報を持たせたい時に使います。

HTML
<button class="js-tab-button" type="button" data-tab="news">ニュース</button>
<button class="js-tab-button" type="button" data-tab="event">イベント</button>

JavaScriptでは、datasetから取得できます。

dataset
const tabName = button.dataset.tab;

data-tab="news"は、JavaScriptではbutton.dataset.tabとして読めます。

テキストを変更する

要素のテキストを変更する時は、textContentを使います。

HTML
<button class="js-button" type="button">開く</button>
textContent
button.textContent = "閉じる";

ボタン文言、エラーメッセージ、カウンター表示などで使います。

エラーメッセージ
errorText.textContent = "メールアドレスを入力してください。";

innerHTML

innerHTMLは、HTML文字列として中身を差し替えます。

innerHTML
message.innerHTML = "<strong>送信が完了しました。</strong>";

HTMLタグごと追加できるので便利ですが、注意が必要です。

insertAdjacentHTML

insertAdjacentHTMLは、指定した位置にHTMLを追加します。

insertAdjacentHTML
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は、要素の先頭に追加します。

appendとprepend
list.append(newItem);
list.prepend(importantItem);

お知らせ一覧に新しい項目を追加する、エラーメッセージをフォームに追加する、タグを増やす、といった場面で使います。

remove

removeは、要素を削除します。

要素を削除する
errorMessage.remove();

フォームのエラー表示を消す、閉じるボタンで通知を消す、モーダル内の一時的な要素を消す時に使います。

小さな実装例: エラーメッセージを表示する

ここまでの内容を使って、入力欄が空の時にエラーメッセージを表示する例を見てみましょう。

HTML
<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>
JavaScript
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で要素を取得する
  • setAttributearia-invalidを更新する
  • hiddenでエラーメッセージの表示を切り替える
  • textContentでメッセージを入れる

よくある失敗

DOM操作でよくある失敗を整理しておきます。

  • 対象要素がないのに処理してCannot read properties of nullになる
  • 複数要素があるのにquerySelectorで最初の1つだけ取得してしまう
  • ページ全体から要素を探して、別のコンポーネントの要素まで操作してしまう
  • CSS用クラスをJavaScriptの取得に使い、デザイン修正で壊れる
  • innerHTMLにユーザー入力をそのまま入れてしまう
  • aria-expandedhiddenなどの状態更新を忘れる

DOM操作は「画面が変わればOK」ではありません。

状態が正しく伝わっているか、複数設置でも壊れないか、HTML構造の変更に弱すぎないかも確認しましょう。

この章のまとめ

この章では、Web制作でよく使うDOM操作を学びました。

  • DOMは、JavaScriptから操作できる形になったHTML
  • 要素の取得にはquerySelectorquerySelectorAllをよく使う
  • 複数設置では、コンポーネント内から要素を探すと安全
  • classListで状態クラスを付け外しする
  • getAttributesetAttributeでHTML属性を扱う
  • hiddendisableddata-*は実務でよく使う
  • テキスト変更は基本textContentを使う
  • innerHTMLは便利だが、ユーザー入力を入れる時は注意する

次の章では、ユーザーのクリック、入力、送信、スクロールなどをきっかけに処理を実行するイベント処理を学びます。