DOM操作の基本
querySelector, textContent, classList, createElement を使って JavaScript から HTML を操作する方法を学びます。
このチャプターで学ぶこと
- querySelector / querySelectorAll で要素を取得できる
- textContent / innerHTML で要素の中身を変更できる
- classList で CSS クラスを操作できる
- createElement / appendChild で要素を動的に追加できる
- setAttribute / style で属性やスタイルを変更できる
DOM操作の基本
前のチャプターではJavaScriptの変数、関数、配列といった基礎を学びました。このチャプターでは、いよいよJavaScriptを使ってHTMLを動的に操作する方法を学びます。これがブラウザにおけるJavaScriptの本来の役割です。
DOMとは何か
DOM(Document Object Model) とは、HTMLドキュメントをJavaScriptから操作するためのインターフェースです。ブラウザがHTMLを読み込むと、その内容をツリー構造のオブジェクトとして内部に保持します。JavaScriptはそのオブジェクトツリーにアクセスして、要素の取得・変更・追加・削除ができます。
<!-- このHTML が... -->
<body>
<div id="app">
<h1 class="title">こんにちは</h1>
<p>テキストです</p>
</div>
</body>
上のHTMLはブラウザの中では次のようなツリー構造になります:
document
└── body
└── div#app
├── h1.title ("こんにちは")
└── p ("テキストです")
JavaScriptはこのツリーの各ノード(要素)にアクセスして、中身・スタイル・属性を自由に読み書きできます。
PHPとの考え方の違い
PHPとLaravelでは、サーバー側でBladeテンプレートを使ってHTMLを生成してブラウザに送信します。一度ブラウザに届いたHTMLをPHPが後から変えることはできません。
{{-- Laravelのbladeテンプレート(サーバー側で実行) --}}
<h1>{{ $user->name }}</h1>
<ul>
@foreach($items as $item)
<li>{{ $item->name }}</li>
@endforeach
</ul>
JavaScriptはブラウザ上でHTMLが表示された後に、そのHTMLを書き換えます。ページをリロードせずにコンテンツを変えられるのはこのためです。これはフロントエンド開発における根本的なパラダイムシフトです。
PHP/Laravel: サーバー → HTML生成 → ブラウザに送信(静的)
JavaScript: ブラウザ上でHTMLをリアルタイムに操作(動的)
要素を取得する
DOM操作の第一歩は「操作したい要素をJavaScriptで取得すること」です。
querySelector
document.querySelector() は、CSSセレクターを使って最初にマッチした要素を取得します。現代のJavaScript開発で最も多用されるメソッドです。
// IDで取得(CSSの #id セレクターと同じ書き方)
const app = document.querySelector('#app');
// クラスで取得(CSSの .class セレクターと同じ書き方)
const title = document.querySelector('.title');
// タグ名で取得(最初の h1 だけ取れる)
const heading = document.querySelector('h1');
// data属性で取得(CSSの属性セレクターと同じ書き方)
const card = document.querySelector('[data-id="42"]');
// ネストしたセレクター(CSSと同じ組み合わせが使える)
const btn = document.querySelector('#app .btn-primary');
const firstLi = document.querySelector('ul > li:first-child');
querySelector の強みは、すでに知っているCSSセレクターの書き方をそのまま使えることです。getElementById や getElementsByClassName という古いAPIもありますが、querySelector ひとつで全て対応できるので、基本的にはこちらだけ覚えれば十分です。
要素が見つからない場合は null が返ります。操作する前に確認するのが安全です。
const el = document.querySelector('.maybe-missing');
if (el) {
// 要素が存在する場合だけ操作する
el.textContent = '見つかりました';
}
querySelectorAll
document.querySelectorAll() は、マッチしたすべての要素を NodeList として返します。
// すべての .card 要素を取得
const cards = document.querySelectorAll('.card');
// NodeList は forEach で繰り返せる
cards.forEach((card) => {
console.log(card.textContent);
});
// インデックスでアクセスもできる
console.log(cards[0]); // 最初の要素
console.log(cards.length); // 要素数
querySelectorAll が返す NodeList は配列に似ていますが、map や filter などの配列メソッドは使えません(forEach は使えます)。配列として扱いたい場合は Array.from(cards) や [...cards] で変換できます。
// 配列に変換してmapを使う
const texts = Array.from(cards).map((card) => card.textContent); 要素の中身を変える
要素を取得したら、その中身を読み書きできます。
textContent
textContent は要素内のテキストを取得・設定します。HTMLタグはそのままテキストとして扱われます。
const title = document.querySelector('h1');
// テキストを読む
console.log(title.textContent); // "こんにちは"
// テキストを書き換える
title.textContent = 'はじめまして';
// 数値や変数も文字列に変換して設定できる
const count = 42;
document.querySelector('#count').textContent = count;
innerHTML
innerHTML は要素内のHTML文字列を取得・設定します。HTMLとして解釈されるため、タグを含むコンテンツを設定できます。
const container = document.querySelector('#container');
// HTMLを設定する
container.innerHTML = '<strong>太字のテキスト</strong>';
// 複数の要素もまとめて設定できる
container.innerHTML = `
<h2>タイトル</h2>
<p>本文テキストです。</p>
`;
innerHTML にユーザーが入力した文字列をそのまま設定すると、XSS(クロスサイトスクリプティング)攻撃の脆弱性につながります。
これはLaravelのBladeテンプレートでいう {{ }} と {!! !!} の違いと同じです。
{{-- Laravel: {{ }} は自動エスケープ(安全) --}}
{{ $userInput }}
{{-- Laravel: {!! !!} はエスケープなし(危険) --}}
{!! $userInput !!}// textContent は自動エスケープ(安全)
el.textContent = userInput; // HTMLタグがそのまま文字として表示される
// innerHTML はエスケープなし(ユーザー入力には使わない)
el.innerHTML = userInput; // <script>タグなども実行される可能性があるルール:ユーザーが入力したデータを表示するときは必ず textContent を使う。innerHTML は固定のHTMLテンプレートを設定するときだけ使う。
CSSクラスを操作する
classList を使うと、要素のCSSクラスを動的に追加・削除・切り替えできます。JavaScriptでUIの状態を視覚的に表現するときに頻繁に使います。
const button = document.querySelector('#my-button');
// クラスを追加
button.classList.add('active');
// クラスを削除
button.classList.remove('active');
// クラスがあれば削除、なければ追加(トグル)
button.classList.toggle('open');
// クラスが存在するか確認(true/false)
if (button.classList.contains('disabled')) {
console.log('このボタンは無効です');
}
// クラスを別のクラスに置き換える
button.classList.replace('btn-primary', 'btn-secondary');
実践例:表示・非表示の切り替え
よくあるパターンとして、クリックで要素の表示・非表示を切り替える実装を見てみましょう。
<!-- HTML -->
<button id="toggle-btn">詳細を表示</button>
<div id="detail" class="hidden">
<p>ここに詳細コンテンツが表示されます。</p>
</div>
/* CSS */
.hidden {
display: none;
}
// JavaScript
const btn = document.querySelector('#toggle-btn');
const detail = document.querySelector('#detail');
btn.addEventListener('click', () => {
// .hidden クラスをトグルする
detail.classList.toggle('hidden');
// ボタンのテキストも変える
if (detail.classList.contains('hidden')) {
btn.textContent = '詳細を表示';
} else {
btn.textContent = '詳細を隠す';
}
});
addEventListener の詳細は次のチャプターで学びます。ここでは「クリックしたときに何かをする」という書き方として見ておいてください。
要素を作って追加する
document.createElement() を使うと、JavaScriptでHTML要素を一から作成してDOMに追加できます。サーバーからデータを取得してリストを動的に構築するようなケースで使います。
// 1. 要素を作る
const li = document.createElement('li');
// 2. 中身とクラスを設定する
li.textContent = '新しいリストアイテム';
li.classList.add('list-item');
// 3. 親要素に追加する(末尾)
const ul = document.querySelector('#my-list');
ul.appendChild(li);
よく使う追加・削除メソッド
const parent = document.querySelector('#container');
const newEl = document.createElement('div');
newEl.textContent = '新しい要素';
// 末尾に追加
parent.appendChild(newEl);
// 先頭に追加
parent.prepend(newEl);
// 要素を削除
newEl.remove();
// 特定の要素の前に挿入
const referenceEl = document.querySelector('#reference');
parent.insertBefore(newEl, referenceEl);
実践例:配列からリストを構築する
データの配列を受け取って、DOM要素を動的に生成するパターンです。
const fruits = ['りんご', 'バナナ', 'オレンジ', 'ぶどう'];
const ul = document.querySelector('#fruit-list');
// forEach で各アイテムのDOM要素を作って追加
fruits.forEach((fruit) => {
const li = document.createElement('li');
li.textContent = fruit;
ul.appendChild(li);
});
Laravelで言えば、@foreach でBladeテンプレートを展開するのと同じ発想です。ただしこちらはサーバーではなくブラウザ上でHTML要素を生成しています。
属性とスタイルを操作する
setAttribute / getAttribute
const img = document.querySelector('img');
// 属性を設定する
img.setAttribute('src', '/images/photo.jpg');
img.setAttribute('alt', '写真の説明');
// 属性を読む
const src = img.getAttribute('src');
console.log(src); // "/images/photo.jpg"
// 属性を削除する
img.removeAttribute('alt');
// 真偽値属性(disabled, checked など)
const input = document.querySelector('input');
input.setAttribute('disabled', ''); // 無効化
input.removeAttribute('disabled'); // 有効化
data-* 属性(dataset)
HTMLの data-* カスタム属性は、JavaScript側から dataset プロパティで簡単にアクセスできます。
<!-- HTMLでdata属性を設定 -->
<button data-user-id="123" data-action="delete">削除</button>
const btn = document.querySelector('button');
// ケバブケース(data-user-id)は キャメルケース(userId)に変換される
console.log(btn.dataset.userId); // "123"
console.log(btn.dataset.action); // "delete"
// 書き込みもできる
btn.dataset.status = 'pending';
// → <button data-status="pending"> になる
element.style でスタイルを直接指定する
const box = document.querySelector('#box');
// CSSプロパティはキャメルケースで指定する
box.style.backgroundColor = '#3b82f6'; // background-color
box.style.fontSize = '1.5rem'; // font-size
box.style.display = 'none'; // display
// 読み取りもできる
console.log(box.style.backgroundColor);
element.style で直接スタイルを書くと、スタイルがJavaScriptのコードに散らばり管理が難しくなります。基本的にはCSSクラスを classList で付け外しする方法を使い、スタイルはCSSファイルに書くようにしましょう。style の直接設定は、ユーザーの操作によって決まる動的な値(座標、サイズなど)を設定するときだけ使うのが理想です。
JavaScriptのオブジェクトからプロフィールカードを動的に生成して、#app 要素に追加してみましょう。
用意するHTML:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>プロフィールカード</title>
<style>
.card { border: 1px solid #e2e8f0; border-radius: 12px; padding: 1.5rem; max-width: 400px; }
.card h2 { margin: 0 0 0.25rem; }
.card .role { color: #64748b; margin: 0 0 0.75rem; font-size: 0.875rem; }
.card .bio { margin: 0 0 1rem; }
.skills { display: flex; gap: 0.5rem; flex-wrap: wrap; list-style: none; padding: 0; margin: 0; }
.skills li { background: #eff6ff; color: #2563eb; padding: 0.2rem 0.75rem; border-radius: 999px; font-size: 0.875rem; }
</style>
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>作成するJavaScript(main.js):
// プロフィールデータ
const profile = {
name: '山田太郎',
role: 'フルスタックエンジニア',
bio: 'LaravelとReactが得意なエンジニアです。最近はTypeScriptの勉強中。',
skills: ['Laravel', 'React', 'MySQL', 'Docker'],
};
// ここからDOM操作で要素を作る
const app = document.querySelector('#app');
// 1. カードのdivを作る
const card = document.createElement('div');
card.classList.add('card');
// 2. 名前の見出しを作る
const name = document.createElement('h2');
name.textContent = profile.name;
// 3. 役職のpを作る
const role = document.createElement('p');
role.classList.add('role');
role.textContent = profile.role;
// 4. 自己紹介のpを作る
const bio = document.createElement('p');
bio.classList.add('bio');
bio.textContent = profile.bio;
// 5. スキルリストを作る(ulとliの組み合わせ)
const skillList = document.createElement('ul');
skillList.classList.add('skills');
profile.skills.forEach((skill) => {
const li = document.createElement('li');
li.textContent = skill;
skillList.appendChild(li);
});
// 6. カードに全部追加する
card.appendChild(name);
card.appendChild(role);
card.appendChild(bio);
card.appendChild(skillList);
// 7. appに追加する
app.appendChild(card);発展課題: データを複数人分の配列にして、forEach でカードを複数枚生成してみましょう。
const members = [
{ name: '山田太郎', role: 'バックエンド', bio: 'Laravel歴5年', skills: ['Laravel', 'PHP'] },
{ name: '田中花子', role: 'フロントエンド', bio: 'ReactとVueが得意', skills: ['React', 'Vue'] },
];
members.forEach((member) => {
// 上記のカード生成ロジックを関数化して呼び出す
}); まとめ
このチャプターで学んだDOM操作の基本をまとめます:
- 要素取得:
document.querySelector(cssSelector)がメインのAPI。CSSセレクターをそのまま使える - 複数取得:
document.querySelectorAll()でNodeListを返す。forEachで繰り返せる - 中身の変更: テキストのみは
textContent(安全)。HTMLを含む場合はinnerHTML(ユーザー入力には使わない) - クラス操作:
classList.add/remove/toggle/containsでCSSクラスを制御する - 要素の作成・追加:
document.createElement()→ 設定 →appendChild()の流れ - 属性操作:
setAttribute/getAttribute、data-*属性はdatasetでアクセスできる - スタイル: 直接の
style指定よりclassListを使ったクラス付け外しを優先する
次のチャプターでは、クリックや入力などのイベント処理を学びます。DOM操作とイベントを組み合わせることで、本格的なインタラクティブなUIが作れるようになります。