はい、承知いたしました。HTML要素をJavaScriptで動的に変更する方法について、約5000語の詳細な記事を作成します。
HTML要素を動的に変更!JavaScript書き換えの基本
ウェブページは、私たちがインターネットを利用する上で欠かせないものです。そして、現代のウェブページは静的な情報表示だけでなく、ユーザーの操作に応じたインタラクティブな振る舞いが求められています。クリック、スクロール、入力といったユーザーの行動に応じて、ページの見た目や内容が変化する。この「動的な」変化を実現する鍵となるのが、JavaScriptによるHTML要素の操作です。
本記事では、ウェブページの構造を司るHTML要素を、クライアントサイドスクリプトであるJavaScriptを使ってどのように「書き換える」か、その基本的な概念から具体的な手法までを、初心者にも分かりやすく、しかし網羅的に解説します。約5000語というボリュームで、DOMの基本から要素の取得、内容・属性の変更、生成、削除、さらにはイベント処理まで、動的HTML操作の基礎を徹底的に掘り下げていきます。
これを読めば、あなたも静的なHTMLだけのページから卒業し、ユーザーとのインタラクションに富んだ、生き生きとしたウェブページを作成する第一歩を踏み出せるでしょう。
さあ、JavaScriptによるHTML要素操作の世界へ飛び込みましょう。
第1章:動的HTMLの基礎概念 – DOMとは?
ウェブページは、ブラウザが表示する際に「解析」され、メモリ上に一定の構造として保持されます。この構造こそが「DOM(Document Object Model)」です。JavaScriptがHTML要素を操作できるのは、このDOMという「モデル」を通じて行うからです。
1.1 ウェブページの構造とDOM
HTML文書は、タグを使って要素を階層構造で定義します。例えば、<html>タグの中に<head>や<body>があり、<body>の中に<header>、<main>、<footer>があり、<main>の中に<section>や<div>、<p>、<img>といった要素が含まれる、といった具合です。
この階層構造は、コンピュータの世界では「ツリー構造」として表現するのが一般的です。DOMはまさに、このHTML文書の構造を「ノード」と呼ばれる単位の集まりからなるツリーとして表現したものです。
- ドキュメントノード: 文書全体(
<html>タグ)を表す最上位のノードです。JavaScriptでは通常documentオブジェクトとしてアクセスします。 - 要素ノード: HTMLタグ(
<p>,<div>,<img>など)一つ一つを表すノードです。 - テキストノード: 要素の中に含まれるテキスト内容を表すノードです。
- 属性ノード: 要素の属性(
id,class,src,hrefなど)を表すノードです。
これらのノードが親子関係や兄弟関係を持ってつながり、HTML文書全体の構造を再現しているのがDOMツリーです。JavaScriptは、このDOMツリー上の特定のノードを探し出し、そのノードの内容を変更したり、新しいノードを追加したり、既存のノードを削除したりすることで、ウェブページの表示を動的に変化させることができるのです。
例えば、次のような簡単なHTMLがあるとします。
“`html
Hello, World!
This is a paragraph.
“`
このHTMLは、DOMとしては以下のような(簡略化した)ツリー構造としてブラウザに認識されます。
document
└── <html>
├── <head>
│ └── <title>
│ └── "DOM Example" (テキストノード)
└── <body>
├── <h1> (id="main-title" 属性)
│ └── "Hello, World!" (テキストノード)
└── <p> (class="content" 属性)
└── "This is a paragraph." (テキストノード)
JavaScriptを使って「Hello, World!」というテキストを「こんにちは!」に変えたい場合、まずid="main-title"を持つ<h1>要素ノードをDOMツリーの中から探し出し、その要素ノードのテキストノードの内容を変更する、という手順になります。
1.2 JavaScriptとDOMの関係
JavaScriptはブラウザに組み込まれたスクリプト言語であり、ブラウザが提供するWeb APIの一つとしてDOM操作の機能を持っています。グローバルオブジェクトである window のプロパティとして document オブジェクトが用意されており、この document オブジェクトを通じてDOMツリーへのアクセスが可能です。
“`javascript
// ドキュメントノード()にアクセス
console.log(document);
// body要素にアクセス
console.log(document.body);
// head要素にアクセス
console.log(document.head);
“`
これらのオブジェクトやプロパティは、DOMツリー上の特定の要素ノードやノードコレクション(複数のノードのリスト)を返します。そして、返されたノードオブジェクトには、そのノードを操作するための様々なプロパティやメソッドが用意されています。
例えば、要素ノードオブジェクトには以下のようなプロパティやメソッドがあります。
id,className,tagName(属性情報の取得)textContent,innerHTML(内容の取得・変更)parentElement,children(親子関係の参照)appendChild(),removeChild()(子ノードの追加・削除)setAttribute(),getAttribute()(属性の取得・設定)addEventListener()(イベントリスナーの登録)
以降の章では、これらのプロパティやメソッドを使って具体的にHTML要素を操作する方法を詳しく見ていきます。
重要なポイント:
* DOMはHTML文書をツリー構造として表現したモデル。
* JavaScriptはdocumentオブジェクトを介してDOMにアクセスし、ノードを操作する。
* DOM操作は、ツリー上のノードを探し、そのノードのプロパティやメソッドを使って変更を加えること。
DOMの理解は、JavaScriptによる動的なウェブページ作成の基盤となります。まずは、操作対象となる「要素」をDOMの中から正確に「取得」する方法から学んでいきましょう。
第2章:操作対象の要素を取得する
HTML要素をJavaScriptで変更するには、まずその要素をDOMツリーの中から「見つけ出す」必要があります。JavaScriptには、様々な方法で要素を取得するためのAPIが用意されています。取得方法によって、単一の要素が返される場合と、複数の要素のリスト(コレクション)が返される場合があります。
2.1 IDで要素を取得する: getElementById()
最も一般的で確実な方法の一つが、要素に設定された一意なid属性を使って要素を取得する方法です。
- メソッド:
document.getElementById(id) - 引数: 取得したい要素の
id属性の文字列。 - 戻り値: 指定されたIDを持つ
Elementオブジェクト。該当する要素が存在しない場合はnullを返します。
idはHTMLドキュメント内でユニークであるべきとW3Cの仕様で定められています。そのため、getElementById()は常に単一の要素、あるいはnullを返します。
使用例:
“`html
“`
“`javascript
// id=”message-box” の要素を取得
const messageBox = document.getElementById(‘message-box’);
// 取得できたか確認
if (messageBox) {
console.log(messageBox); //
console.log(messageBox.textContent); // “こんにちは”
} else {
console.log(‘要素が見つかりませんでした’);
}
// 存在しないIDを指定した場合
const nonExistentElement = document.getElementById(‘non-existent’);
console.log(nonExistentElement); // null
“`
このメソッドは非常に高速であり、特定の要素をピンポイントで操作したい場合に最適です。
2.2 クラス名で要素を取得する: getElementsByClassName()
複数の要素に同じスタイルや振る舞いを適用したい場合、class属性が使われます。JavaScriptでは、このクラス名を使って複数の要素をまとめて取得できます。
- メソッド:
document.getElementsByClassName(className) - 引数: 取得したい要素の
class属性に含まれるクラス名の文字列(複数のクラス名を指定する場合はスペースで区切る)。 - 戻り値: 指定されたクラス名を持つ要素の
HTMLCollection(ライブコレクション)を返します。該当する要素が存在しない場合は空のHTMLCollectionを返します。
HTMLCollectionは配列のようにインデックスでアクセスできますが、厳密には配列ではありません(配列のforEachなどは直接使えません)。しかし、forループやfor...ofループを使って要素を順番に処理できます。
使用例:
“`html
- 項目1
- 項目2
- 項目3
これはリスト項目ではありませんが、クラス名は同じです。
“`
“`javascript
// class=”item” の要素を全て取得
const items = document.getElementsByClassName(‘item’);
console.log(items); // HTMLCollectionが出力される (4つの要素が含まれる)
console.log(items.length); // 4
// コレクションの要素にアクセス
console.log(items[0].textContent); // “項目1”
console.log(items[1].textContent); // “項目2”
// 全ての要素をループ処理
for (let i = 0; i < items.length; i++) {
console.log(items[i].textContent);
}
// for…of ループも使用可能
for (const item of items) {
console.log(item.textContent);
}
// 複数のクラス名を指定する場合 (両方のクラスを持つ要素を取得)
const activeItems = document.getElementsByClassName(‘item active’);
console.log(activeItems.length); // 1
console.log(activeItems[0].textContent); // “項目2”
“`
getElementsByClassName()は、同じ特性を持つ複数の要素にまとめてアクセスしたい場合に便利です。
2.3 タグ名で要素を取得する: getElementsByTagName()
特定の種類の要素(例えば、全ての<p>タグ、全ての<a>タグなど)をまとめて取得したい場合に利用します。
- メソッド:
document.getElementsByTagName(tagName) - 引数: 取得したい要素のタグ名(例: ‘p’, ‘div’, ‘a’, ‘img’)。全ての要素を取得したい場合は
'*'を指定します。 - 戻り値: 指定されたタグ名を持つ要素の
HTMLCollection(ライブコレクション)を返します。該当する要素が存在しない場合は空のHTMLCollectionを返します。
getElementsByTagName()もgetElementsByClassName()と同様にHTMLCollectionを返します。
使用例:
“`html
最初の段落。
二番目の段落(divの中)。
別の要素
三番目の段落。
“`
“`javascript
// 全てのp要素を取得
const paragraphs = document.getElementsByTagName(‘p’);
console.log(paragraphs); // HTMLCollectionが出力される (3つのp要素が含まれる)
console.log(paragraphs.length); // 3
// 全ての要素を取得
const allElements = document.getElementsByTagName(‘*’);
console.log(allElements.length); // ページ内の全ての要素数
“`
このメソッドは、特定の種類の要素全体に対して操作を行いたい場合に役立ちます。
2.4 CSSセレクターで要素を取得する: querySelector()
CSSで要素を選択するのと同じ感覚で要素を取得できる、非常に柔軟なメソッドです。指定されたCSSセレクターにマッチする要素のうち、最初に見つかった要素を一つだけ返します。
- メソッド:
document.querySelector(selector) - 引数: 有効なCSSセレクター文字列(例: ‘#id’, ‘.class’, ‘tag’, ‘div > p’, ‘input[type=”text”]’)。
- 戻り値: 指定されたセレクターに最初にマッチした
Elementオブジェクト。該当する要素が存在しない場合はnullを返します。
このメソッドは、単一の要素を取得したい場合に、getElementById以外の様々な条件で要素を指定できるため非常に便利です。
使用例:
“`html
最初のテキスト。
ハイライトされたテキスト。
“`
“`javascript
// IDで取得 (getElementByIdと同じ結果)
const button = document.querySelector(‘#my-button’);
console.log(button.textContent); // “ボタン”
// クラス名で取得 (最初のマッチ)
const firstText = document.querySelector(‘.text’);
console.log(firstText.textContent); // “最初のテキスト。”
// 複雑なセレクター
const highlightedText = document.querySelector(‘.container .highlight’);
console.log(highlightedText.textContent); // “ハイライトされたテキスト。”
// 存在しないセレクター
const nonExistent = document.querySelector(‘.non-existent-class’);
console.log(nonExistent); // null
“`
querySelector()は、要素を特定するための条件がIDだけではない場合や、より簡潔に記述したい場合に強力な選択肢となります。
2.5 CSSセレクターで複数の要素を取得する: querySelectorAll()
querySelector()と同様にCSSセレクターを使用しますが、マッチする全ての要素を取得します。
- メソッド:
document.querySelectorAll(selector) - 引数: 有効なCSSセレクター文字列。
- 戻り値: 指定されたセレクターにマッチする要素の
NodeList(スタティックコレクション)を返します。該当する要素が存在しない場合は空のNodeListを返します。
NodeListもHTMLCollectionと同様にインデックスでアクセスでき、forループやfor...ofループ、そして配列のforEach()メソッドも使用できます。
使用例:
“`html
アイテムB
“`
“`javascript
// 全てのspan要素を取得
const spans = document.querySelectorAll(‘span’);
console.log(spans.length); // 3
// 特定のクラス内のspan要素を全て取得
const listItems = document.querySelectorAll(‘.list span’);
console.log(listItems.length); // 3 (どちらのdiv内のspanも含まれる)
// NodeListの操作 (forEachが使える)
listItems.forEach(item => {
console.log(item.textContent);
});
“`
querySelectorAll()は、特定のパターンにマッチする複数の要素にまとめてアクセスしたい場合に非常に柔軟な方法を提供します。
2.6 HTMLCollection と NodeList の違い (ライブ vs. スタティック)
ここで、getElementsByClassName()やgetElementsByTagName()が返すHTMLCollectionと、querySelectorAll()が返すNodeListの重要な違いに触れておきます。
-
HTMLCollection(ライブコレクション):- DOMの現在の状態を「ライブ」で反映します。つまり、コレクションを取得した後にDOMツリー内で要素が追加または削除されると、その変更が自動的にコレクションに反映されます。
- 要素へのアクセスはインデックス (
[i]) かitem(i)メソッドで行います。 - 基本的なループ (
for,for...of) は使えますが、forEach,map,filterなどの配列メソッドは直接使えません(Array.from()などで配列に変換すれば使えます)。
-
NodeList(スタティックコレクション):- コレクションを取得した時点でのDOMの状態のスナップショットです。コレクションを取得した後にDOMツリー内で要素が追加または削除されても、コレクションの内容は変化しません。
- 要素へのアクセスはインデックス (
[i]) かitem(i)メソッドで行います。 - 多くの
NodeList実装ではforEachメソッドが利用可能です。
例:
“`html
Live Item 1
“`
“`javascript
const liveCollection = document.getElementsByClassName(‘live-item’);
const staticCollection = document.querySelectorAll(‘.live-item’);
console.log(‘初期状態:’);
console.log(‘Live:’, liveCollection.length); // 1
console.log(‘Static:’, staticCollection.length); // 1
// 新しい要素を追加
const container = document.getElementById(‘container’);
const newElement = document.createElement(‘p’);
newElement.className = ‘live-item’;
newElement.textContent = ‘Live Item 2’;
container.appendChild(newElement);
console.log(‘要素追加後:’);
console.log(‘Live:’, liveCollection.length); // 2 (自動的に反映)
console.log(‘Static:’, staticCollection.length); // 1 (追加前の状態を保持)
“`
ほとんどの場合、querySelectorAll()が返すスタティックなNodeListの方が、コレクションを操作している最中にDOMが変更されても影響を受けないため、扱いやすいかもしれません。しかし、リアルタイムのDOMの状態を監視したい場合は、ライブコレクションが役立つこともあります。
取得方法のまとめ:
idで一意な要素:getElementById()(最も高速)- クラス名で複数の要素:
getElementsByClassName()(ライブ) - タグ名で複数の要素:
getElementsByTagName()(ライブ) - CSSセレクターで最初の要素:
querySelector()(柔軟) - CSSセレクターで複数の要素:
querySelectorAll()(柔軟, スタティック)
これらのメソッドを状況に応じて使い分けることで、操作したいHTML要素を正確に取得できます。要素が取得できたら、次はその内容や属性を変更する方法を見ていきましょう。
第3章:要素の内容を変更する
要素を取得したら、その中身(テキストやHTML)を変更してみましょう。主に使われるのはtextContentとinnerHTMLというプロパティです。
3.1 テキスト内容の変更: textContent
要素とその全ての子孫要素に含まれる「テキスト」を取得または設定します。HTMLタグは全て無視され、プレーンテキストとして扱われます。
- プロパティ:
element.textContent - 取得: 要素内の全てのテキストコンテンツ(整形されていない生の値)を文字列として返します。
- 設定: 要素内の既存の子ノードを全て削除し、新しいテキストノードを作成してそのテキストを設定します。
特徴:
- セキュリティが高い: 設定する値にHTMLタグが含まれていても、それは単なる文字列として扱われるため、クロスサイトスクリプティング(XSS)攻撃のリスクが低い。
- パフォーマンスが良い: HTMLを解析する必要がないため、
innerHTMLよりも一般的に高速です。
使用例:
“`html
Hello, World!
“`
“`javascript
const greetingDiv = document.getElementById(‘greeting’);
// 現在のテキスト内容を取得
console.log(greetingDiv.textContent);
// 出力:
// Hello, World!
// (pタグやstrongタグは無視される)
// テキスト内容を変更
greetingDiv.textContent = ‘こんにちは、皆さん!’;
console.log(greetingDiv.innerHTML);
// 出力: こんにちは、皆さん!
// (内部のpタグやstrongタグは置き換えられる)
// HTMLタグを含む文字列を設定してみる (プレーンテキストとして扱われる)
greetingDiv.textContent = ‘これは太字ではありません。’;
console.log(greetingDiv.innerHTML);
// 出力: これは<strong>太字</strong>ではありません。
// (<strong>のようにエスケープされる)
“`
要素内のテキストだけを変更したい場合や、ユーザー入力などの信頼できない値を要素に表示させる場合は、textContentを使用するのが推奨されます。
3.2 HTML内容の変更: innerHTML
要素の内部のHTML構造全体を取得または設定します。設定する値にHTMLタグが含まれている場合、ブラウザはそのタグを解釈してDOM構造を再構築します。
- プロパティ:
element.innerHTML - 取得: 要素の開始タグと終了タグの間のHTMLコードを文字列として返します。
- 設定: 要素内の既存の子ノードを全て削除し、新しいHTML文字列を解析してDOM構造を作成し、その要素の新しい子として追加します。
特徴:
- 柔軟性が高い: テキストだけでなく、新しい要素や構造をまとめて挿入できます。
- セキュリティリスク: 設定する値に悪意のあるスクリプトタグ(例:
<script>alert('XSS')</script>)が含まれている場合、それが実行される可能性があります。信頼できないソースからの値にinnerHTMLを使うのは避けるべきです。 - パフォーマンス: HTMLの解析が必要なため、
textContentよりもコストがかかる場合があります。
使用例:
“`html
元のコンテンツ。
“`
“`javascript
const contentArea = document.getElementById(‘content-area’);
// 現在のHTML内容を取得
console.log(contentArea.innerHTML);
// 出力:
//
//
元のコンテンツ。
//
// HTML内容を変更 (新しいHTMLを挿入)
contentArea.innerHTML = ‘
新しい見出し
新しい段落。
‘;
console.log(contentArea.innerHTML);
// 出力:
//
新しい見出し
新しい段落。
// スクリプトタグを挿入してみる (危険な例!)
// 絶対に信頼できない入力値でこれを行わないこと!
contentArea.innerHTML = ‘‘;
// この場合、onerror属性のJavaScriptが実行される可能性があります。
“`
新しいHTML構造をまとめて要素内に挿入したい場合にはinnerHTMLが便利ですが、必ず信頼できるソースからの文字列にのみ使用する、あるいはユーザー入力などを挿入する際は十分にサニタイズ(無害化)するというセキュリティ上の注意が必要です。多くの場合は、後述するDOM要素の生成・追加メソッド (createElement, appendChildなど) を使う方が安全で推奨されます。
3.3 innerText について (補足)
innerTextというプロパティも存在し、textContentに似ています。しかし、innerTextはCSSによって要素が非表示になっているかなどを考慮し、ブラウザが実際に表示しているテキストのみを返します。また、テキスト整形(改行やスペース)の扱いもtextContentと異なります。
textContent: 要素ツリー内の全てのテキストノードを連結したもの。非表示要素のテキストも含む。CSSの整形を考慮しない。innerText: 要素のレンダリングされたテキスト。非表示要素のテキストは含まない。CSSの整形(改行やスペースの扱い)を考慮する。パフォーマンスはtextContentより低い場合がある。
特別な理由がない限り、テキスト内容の取得・設定にはtextContentを使うのが一般的で推奨されています。
内容変更のまとめ:
- テキストのみを安全に変更:
element.textContent = '新しいテキスト' - HTML構造を含めて変更:
element.innerHTML = '新しいHTML'(セキュリティに注意!)
これで要素の中身を変更する方法を学びました。次は、要素の属性(ID, クラス, 画像パスなど)を変更する方法です。
第4章:要素の属性を変更する
HTML要素の属性(id, class, src, href, styleなど)もJavaScriptで動的に変更できます。これにより、要素の見た目や振る舞いを細かく制御できます。
4.1 共通属性の直接アクセス
多くの標準的な属性は、要素オブジェクトのプロパティとして直接アクセスできます。
element.idelement.classNameelement.src(for<img>,<script>etc.)element.href(for<a>,<link>etc.)element.value(for form elements like<input>,<textarea>,<select>)element.disabled(boolean for form elements)element.checked(boolean for checkbox/radio)- など
これらのプロパティを使うと、属性の値の取得や設定が簡単に行えます。
使用例:
html
<img id="my-image" src="image1.png" alt="Image 1">
<a id="my-link" href="old-page.html">リンク</a>
<input type="text" id="my-input" value="初期値">
“`javascript
const myImage = document.getElementById(‘my-image’);
const myLink = document.getElementById(‘my-link’);
const myInput = document.getElementById(‘my-input’);
// 属性の値を取得
console.log(myImage.src); // 画像の完全なURL (ブラウザが解決したパス)
console.log(myImage.alt); // “Image 1”
console.log(myLink.href); // リンクの完全なURL
console.log(myInput.value); // “初期値”
// 属性の値を設定
myImage.src = ‘image2.png’; // 画像を変更
myImage.alt = ‘Image 2’; // altテキストを変更
myLink.href = ‘new-page.html’; // リンク先を変更
myInput.value = ‘新しい値’; // 入力欄の値を変更
“`
これらのプロパティを使うのは、対応する属性が標準的で頻繁に使用される場合に効率的です。
4.2 任意の属性を取得・設定・削除する: getAttribute, setAttribute, removeAttribute
上記のような直接アクセスできるプロパティがない属性(例えば、data-*属性や、あまり一般的でない属性)や、属性の存在そのものを操作したい場合には、汎用的な属性操作メソッドを使用します。
element.getAttribute(attributeName):- 指定された属性の値を取得します。属性が存在しない場合は
nullまたは空文字列を返します。 - 引数: 取得したい属性名の文字列。
- 指定された属性の値を取得します。属性が存在しない場合は
element.setAttribute(attributeName, value):- 指定された属性の値を設定します。属性が既に存在する場合は上書きされ、存在しない場合は新しく追加されます。
- 引数: 設定したい属性名の文字列、設定したい値の文字列。
element.removeAttribute(attributeName):- 指定された属性を削除します。
- 引数: 削除したい属性名の文字列。
使用例:
“`html
“`
“`javascript
const dataBox = document.getElementById(‘data-box’);
// 属性の値を取得
console.log(dataBox.getAttribute(‘id’)); // “data-box”
console.log(dataBox.getAttribute(‘data-info’)); // “secret”
console.log(dataBox.getAttribute(‘custom-attr’)); // “something”
console.log(dataBox.getAttribute(‘non-existent’)); // null
// 属性の値を設定 (変更・追加)
dataBox.setAttribute(‘data-info’, ‘public’); // 値を変更
dataBox.setAttribute(‘new-attr’, ‘added’); // 新しい属性を追加
console.log(dataBox.outerHTML);
// 出力例:
// 属性を削除
dataBox.removeAttribute(‘custom-attr’);
dataBox.removeAttribute(‘non-existent’); // 存在しない属性の削除はエラーにならない
console.log(dataBox.outerHTML);
// 出力例:
“`
getAttributeと直接プロパティでアクセスする方法には違いがあります。例えば、href属性の場合、getAttribute('href')はHTMLソースに書かれたそのままの値を返しますが、element.hrefはブラウザが解決した絶対URLを返します。多くの場合はプロパティでのアクセスの方が便利ですが、HTMLソース通りの値が必要な場合はgetAttributeを使います。
特にdata-*属性は、datasetプロパティを使ってもっと簡単にアクセスすることもできます (element.dataset.infoなど)。
4.3 CSSクラスの操作: classList
要素のclass属性は、複数のクラス名をスペース区切りで持ちうるため、文字列として直接操作するのは面倒でバグの元になりがちです (element.className = 'new-class'や、文字列操作による追加/削除など)。より安全で便利な方法として、classListプロパティを使用します。
element.classListは、要素のクラス名リストを管理するための便利なメソッド群を提供します。
element.classList.add(className1, className2, ...):- 指定されたクラス名を要素に追加します。既にあるクラス名は重複して追加されません。
element.classList.remove(className1, className2, ...):- 指定されたクラス名を要素から削除します。存在しないクラス名を指定してもエラーになりません。
element.classList.toggle(className, force):- 指定されたクラス名が存在すれば削除し、存在しなければ追加します。第2引数
forceにtrueを指定すると追加だけ、falseを指定すると削除だけを行います。
- 指定されたクラス名が存在すれば削除し、存在しなければ追加します。第2引数
element.classList.contains(className):- 指定されたクラス名が要素に存在するかどうかを真偽値で返します。
element.classList.replace(oldClass, newClass):- 既存のクラス名を新しいクラス名に置き換えます。
使用例:
“`html
“`
“`javascript
const toggleButton = document.getElementById(‘toggle-button’);
const statusMessage = document.getElementById(‘status-message’);
// クラスを追加
toggleButton.classList.add(‘active’);
console.log(toggleButton.className); // “button inactive active” (順序は保証されない)
// クラスを削除
toggleButton.classList.remove(‘inactive’);
console.log(toggleButton.className); // “button active”
// クラスをトグル (hiddenがあれば削除、なければ追加)
statusMessage.classList.toggle(‘hidden’); // hiddenが削除される
console.log(statusMessage.className); // “”
statusMessage.classList.toggle(‘hidden’); // hiddenが追加される
console.log(statusMessage.className); // “hidden”
// クラスの存在を確認
console.log(toggleButton.classList.contains(‘active’)); // true
console.log(statusMessage.classList.contains(‘visible’)); // false
// クラスを置き換え (存在すれば)
toggleButton.classList.replace(‘button’, ‘btn’);
console.log(toggleButton.className); // “btn active”
“`
CSSクラスの追加・削除は、要素のスタイルを動的に切り替える際によく使われるテクニックです。classListを使うことで、安全かつ簡潔にクラス名を操作できます。
4.4 インラインCSSスタイルの変更: style
要素の見た目をJavaScriptで直接変更したい場合、element.styleプロパティを使用します。これにより、HTMLのstyle属性に直接CSSプロパティを設定するのと同じ効果が得られます。
element.styleはCSSStyleDeclarationオブジェクトであり、CSSプロパティ名に対応するプロパティを持ちます。CSSプロパティ名がハイフンを含む場合(例: background-color)は、JavaScriptのプロパティ名ではキャメルケース(例: backgroundColor)に変換して使用します。
- プロパティ:
element.style.cssPropertyName - 取得: 設定されているインラインスタイルの値を文字列で取得します。
- 設定: インラインスタイルを設定します。
注意点:
- この方法で設定されるのはインラインスタイルです。CSSファイルや
<style>タグで定義されたスタイルよりも優先されます。 - 多数のスタイルを変更する場合や、複雑なスタイルを適用する場合は、スタイルを定義したCSSクラスを
classListで切り替える方が、管理がしやすく効率的です。JavaScriptで直接スタイルを設定するのは、特定の瞬間的な変更(例: アニメーション中の位置変更)などに限定するのが一般的です。
使用例:
“`html
“`
“`javascript
const styledBox = document.getElementById(‘styled-box’);
// 現在のスタイルを取得
console.log(styledBox.style.width); // “100px”
console.log(styledBox.style.backgroundColor); // “lightblue”
// スタイルを変更
styledBox.style.backgroundColor = ‘salmon’; // 背景色を変更
styledBox.style.marginTop = ’20px’; // 新しいスタイルを追加
styledBox.style.border = ‘2px solid black’; // 複数のスタイルをまとめて設定
// スタイルを削除 (値を空文字列に設定)
styledBox.style.width = ”; // widthスタイルを削除 (インラインスタイルのみ)
console.log(styledBox.outerHTML);
// 出力例:
“`
element.styleは手軽にスタイルを変更できますが、大規模なアプリケーションではCSSクラスによる管理を優先すべきです。
属性変更のまとめ:
- 一般的な属性: 対応するプロパティ (
id,src,valueなど) を直接使う。 - 任意の属性:
getAttribute,setAttribute,removeAttributeを使う。data-*属性はdatasetも便利。 - CSSクラス:
classListのadd,remove,toggle,contains,replaceを使う (推奨)。 - インラインスタイル:
element.style.cssPropertyNameを使う (一時的な変更や簡単な場合に限定)。
これで、要素の内容や属性を自由に変更できるようになりました。次は、新しい要素をページに追加したり、既存の要素を削除したりする方法を見ていきましょう。
第5章:新しい要素を作成・挿入する
既存の要素を変更するだけでなく、新しい要素を動的に作成し、DOMツリー内の任意の場所に挿入することもよく行われます。例えば、リストに新しい項目を追加したり、ユーザーのアクションに応じてモーダルダイアログを表示したりする場合などです。
新しい要素を作成し、ページに挿入するには、主に以下の手順を踏みます。
1. 新しい要素ノードを作成する。
2. 必要に応じて、作成した要素の内容や属性を設定する。
3. DOMツリー内の既存の親要素や参照要素を指定し、作成した要素を挿入する。
5.1 要素ノードの作成: document.createElement()
指定したタグ名を持つ新しい要素ノードを作成しますが、この時点ではまだDOMツリーには追加されていません。
- メソッド:
document.createElement(tagName, options) - 引数: 作成したい要素のタグ名の文字列(例: ‘div’, ‘p’, ‘span’, ‘li’)。オプション引数もありますが、通常は省略します。
- 戻り値: 作成された新しい
Elementオブジェクト。
使用例:
“`javascript
// 新しいdiv要素を作成
const newDiv = document.createElement(‘div’);
// 新しいp要素を作成
const newParagraph = document.createElement(‘p’);
// 新しい画像要素を作成
const newImage = document.createElement(‘img’);
console.log(newDiv); // 新しいdiv要素オブジェクトが出力される
console.log(newParagraph); // 新しいp要素オブジェクトが出力される
“`
作成された要素は、取得した既存の要素と同様にプロパティやメソッドを持ちます。
5.2 テキストノードの作成: document.createTextNode()
要素ノードの「内容」として、プレーンテキストを表すテキストノードを作成できます。
- メソッド:
document.createTextNode(text) - 引数: テキストノードに含めたい文字列。
- 戻り値: 作成された新しい
Textオブジェクト。
使用例:
“`javascript
// テキストノードを作成
const textNode = document.createTextNode(‘これはテキストノードです。’);
console.log(textNode); // テキストノードオブジェクトが出力される
“`
作成したテキストノードは、要素ノードの子として追加することで、その要素のテキスト内容となります。
5.3 DOMツリーへの挿入
作成した要素ノードは、既存の要素の子として、あるいは既存の要素の兄弟として挿入することで、初めてページに表示されます。いくつかの挿入方法があります。
5.3.1 末尾の子として追加: parentElement.appendChild()
指定した親要素の最後の子として、作成した要素ノードを追加します。
- メソッド:
parentElement.appendChild(childElement) - 引数: 親要素に追加したい子要素ノード。
- 戻り値: 追加された子要素ノード。
使用例:
“`html
- 既存の項目
“`
“`javascript
const myList = document.getElementById(‘my-list’);
// 新しいリスト項目を作成
const newListItem = document.createElement(‘li’);
newListItem.textContent = ‘新しい項目’; // テキスト内容を設定
// リストの末尾に追加
myList.appendChild(newListItem);
console.log(myList.innerHTML);
// 出力:
//
“`
appendChildは最も基本的でよく使われる挿入方法です。
5.3.2 指定した要素の前に挿入: parentElement.insertBefore()
指定した親要素の子として、作成した要素ノードを、既存の参照要素の前に挿入します。
- メソッド:
parentElement.insertBefore(newNode, referenceNode) - 引数: 挿入したい新しいノード (
newNode)、挿入位置の基準となる既存の子ノード (referenceNode)。referenceNodeにnullを指定すると、appendChildと同じように末尾に追加されます。 - 戻り値: 挿入された新しいノード。
使用例:
“`html
- 項目2
- 項目3
“`
“`javascript
const myListInsert = document.getElementById(‘my-list-insert’);
const itemTwo = document.getElementById(‘item-two’);
// 新しいリスト項目を作成
const newItemOne = document.createElement(‘li’);
newItemOne.textContent = ‘項目1’;
// 項目2の前に挿入
myListInsert.insertBefore(newItemOne, itemTwo);
console.log(myListInsert.innerHTML);
// 出力:
//
“`
既存の要素の前に挿入したい場合に便利です。
5.3.3 より柔軟な位置への挿入: element.insertAdjacentElement()
既存の要素に対して、その要素の外側や内側の特定の位置に別の要素を挿入できます。
- メソッド:
element.insertAdjacentElement(position, element) - 引数:
position: 挿入位置を指定する文字列。以下のいずれか。'beforebegin':element自身の直前(elementはelementの前の兄弟になる)。'afterbegin':element自身の開始タグ直後(elementの最初の子になる)。'beforeend':element自身の終了タグ直前(elementの最後の子になる)。これはappendChildとほぼ同じ。'afterend':element自身の直後(elementはelementの次の兄弟になる)。
element: 挿入したい要素ノード。
- 戻り値: 挿入された要素ノード。挿入に失敗した場合は
null。
使用例:
“`html
元のコンテンツ
“`
“`javascript
const targetElement = document.getElementById(‘target-element’);
const beforeElement = document.createElement(‘p’);
beforeElement.textContent = ‘ターゲット要素の前に追加’;
const afterBeginElement = document.createElement(‘p’);
afterBeginElement.textContent = ‘ターゲット要素の最初の子として追加’;
const beforeEndElement = document.createElement(‘p’);
beforeEndElement.textContent = ‘ターゲット要素の最後の子として追加’;
const afterElement = document.createElement(‘p’);
afterElement.textContent = ‘ターゲット要素の後に追加’;
// 挿入実行
targetElement.insertAdjacentElement(‘beforebegin’, beforeElement);
targetElement.insertAdjacentElement(‘afterbegin’, afterBeginElement);
targetElement.insertAdjacentElement(‘beforeend’, beforeEndElement);
targetElement.insertAdjacentElement(‘afterend’, afterElement);
/*
結果のHTML構造:
ターゲット要素の前に追加
ターゲット要素の最初の子として追加
元のコンテンツ
ターゲット要素の最後の子として追加
ターゲット要素の後に追加
*/
“`
insertAdjacentElementは、appendChildやinsertBeforeよりも柔軟な挿入位置を指定できるため便利です。同様にinsertAdjacentHTMLやinsertAdjacentTextというメソッドもあり、HTML文字列やテキスト文字列を直接指定の位置に挿入できます(innerHTMLと同様にセキュリティに注意が必要です)。
5.3.4 複数のノードを一度に追加 (モダンな方法): element.append(), element.prepend()
appendChildは一度に一つのノードしか追加できませんでしたが、新しいappend()およびprepend()メソッドを使うと、複数のノードやテキスト文字列を一度に、それぞれ子ノードの末尾または先頭に追加できます。
element.append(nodeOrString1, nodeOrString2, ...):- 指定したノードや文字列を、その要素の最後の子として追加します。
element.prepend(nodeOrString1, nodeOrString2, ...):- 指定したノードや文字列を、その要素の最初の子として追加します。
これらのメソッドは親要素を指定して使用します。
使用例:
“`html
既存のコンテンツ
“`
“`javascript
const testDiv = document.getElementById(‘append-prepend-test’);
const newNode1 = document.createElement(‘span’);
newNode1.textContent = ‘新しいSpan 1’;
const newNode2 = document.createElement(‘span’);
newNode2.textContent = ‘新しいSpan 2’;
// 末尾に複数のノードと文字列を追加
testDiv.append(‘末尾テキスト, ‘, newNode1, newNode2);
console.log(testDiv.innerHTML);
// 出力:
//
既存のコンテンツ
末尾テキスト, 新しいSpan 1新しいSpan 2
// 先頭に複数のノードと文字列を追加
const prependNode1 = document.createElement(‘strong’);
prependNode1.textContent = ‘先頭のStrong’;
testDiv.prepend(prependNode1, ‘先頭テキスト, ‘);
console.log(testDiv.innerHTML);
// 出力:
// 先頭のStrong先頭テキスト,
既存のコンテンツ
末尾テキスト, 新しいSpan 1新しいSpan 2
“`
append()とprepend()は、複数の要素やテキストを効率的に追加できるため、モダンなJavaScript開発でよく使われます。引数にテキスト文字列を渡した場合、自動的にテキストノードに変換されます。
5.3.5 自身を基準とした挿入 (モダンな方法): element.before(), element.after()
append()やprepend()が「親要素」に対して使うのに対し、before()やafter()は「自分自身」を基準にして、その要素の兄弟要素としてノードや文字列を挿入します。
element.before(nodeOrString1, nodeOrString2, ...):- 指定したノードや文字列を、その要素自身の直前に挿入します。
element.after(nodeOrString1, nodeOrString2, ...):- 指定したノードや文字列を、その要素自身の直後に挿入します。
これらのメソッドは、挿入したい要素ノード自身に対して使用します。
使用例:
“`html
“`
“`javascript
const targetSibling = document.getElementById(‘target-sibling’);
const beforeSibling = document.createElement(‘strong’);
beforeSibling.textContent = ‘ターゲットの前に追加’;
const afterSibling = document.createElement(‘em’);
afterSibling.textContent = ‘ターゲットの後に追加’;
// ターゲット要素の直前にノードと文字列を挿入
targetSibling.before(‘前のテキスト, ‘, beforeSibling);
// ターゲット要素の直後にノードと文字列を挿入
targetSibling.after(afterSibling, ‘, 後ろのテキスト’);
console.log(targetSibling.parentElement.innerHTML); // 親要素の中身を確認
/*
結果のHTML構造:
前のテキスト, ターゲットの前に追加ターゲットターゲットの後に追加, 後ろのテキスト
*/
“`
before()とafter()は、挿入したい要素の「隣」に要素を追加したい場合に直感的で便利です。
5.4 DocumentFragment (パフォーマンス向上テクニック)
多数の要素をまとめてDOMに追加する場合、要素を一つずつ追加するたびにブラウザはDOMツリーの変更を処理するため、パフォーマンスが悪化することがあります。このような場合に有効なテクニックとしてDocumentFragmentを使用できます。
DocumentFragmentは、DOMノードの一時的なコンテナとして機能します。要素を作成し、DocumentFragmentの中に構築しておき、最後にDocumentFragment全体を一度だけDOMツリーに追加します。DocumentFragment自身がDOMツリーに追加されることはなく、その子ノードだけが移動されます。
- 作成:
document.createDocumentFragment()
使用例 (appendChildを繰り返す場合との比較):
“`html
“`
“`javascript
const listFragment = document.getElementById(‘fragment-test’);
const fragment = document.createDocumentFragment(); // DocumentFragmentを作成
// 大量のリスト項目を作成し、DocumentFragmentに追加
for (let i = 0; i < 1000; i++) {
const li = document.createElement(‘li’);
li.textContent = 項目 ${i + 1};
fragment.appendChild(li); // DocumentFragmentに追加 (この時点ではDOMに影響なし)
}
// DocumentFragment全体をDOMツリーに一度だけ追加
listFragment.appendChild(fragment); // DocumentFragmentの子ノードが一気に移動される
// ※ DocumentFragmentを使わない場合:
// for (let i = 0; i < 1000; i++) {
// const li = document.createElement(‘li’);
// li.textContent = 項目 ${i + 1};
// listFragment.appendChild(li); // 1000回DOM操作が発生する可能性がある
// }
“`
多数の要素を生成して追加するような処理では、DocumentFragmentを使用することでDOM操作の回数を減らし、パフォーマンスを向上させることができます。
要素作成・挿入のまとめ:
- 要素の作成:
document.createElement('tagname') - テキストの作成:
document.createTextNode('text') - 子要素の末尾に追加:
parentElement.appendChild(childElement) - 子要素の先頭または末尾に複数追加:
parentElement.append(nodesOrStrings)/parentElement.prepend(nodesOrStrings)(モダン) - 指定した子要素の前に挿入:
parentElement.insertBefore(newNode, referenceNode) - 自分自身の前後または内部の特定位置に挿入:
element.insertAdjacentElement('position', element) - 自分自身の兄弟として前後に追加:
element.before(nodesOrStrings)/element.after(nodesOrStrings)(モダン) - 多数の要素を効率的に追加:
document.createDocumentFragment()を使用する。
これらのメソッドを組み合わせることで、ページの構造を自在に変化させることができます。
第6章:要素を削除する
動的に追加された要素や、不要になった要素をページから削除することもよく行われます。要素を削除する方法もいくつかあります。
6.1 親要素から子要素を削除する: parentElement.removeChild()
削除したい要素の親要素から、その子要素を削除します。
- メソッド:
parentElement.removeChild(childElement) - 引数: 削除したい子要素ノード。
- 戻り値: 削除された子要素ノード。
このメソッドを使うには、削除したい要素だけでなく、その親要素を取得しておく必要があります。
使用例:
“`html
- 項目A
- 項目B
- 項目C
“`
“`javascript
const removeList = document.getElementById(‘remove-list’);
const itemB = document.getElementById(‘item-b’);
// 親要素から子要素を削除
if (removeList && itemB) {
removeList.removeChild(itemB);
}
console.log(removeList.innerHTML);
// 出力:
//
“`
削除したい要素が親要素の中に確実に存在することを確認してから実行する必要があります。
6.2 要素自身を削除する (モダンな方法): element.remove()
削除したい要素自身に対して呼び出すことで、親要素からその要素を削除します。removeChildよりも直感的で簡潔な方法です。
- メソッド:
element.remove() - 引数: なし
- 戻り値: なし
このメソッドは、削除したい要素への参照さえあれば、親要素を別途取得する必要がないため便利です。
使用例:
“`html
この段落は削除されます。
“`
“`javascript
const paragraphToRemove = document.getElementById(‘paragraph-to-remove’);
// 要素自身を削除
if (paragraphToRemove) {
paragraphToRemove.remove();
}
const removeContainer = document.getElementById(‘remove-container’);
console.log(removeContainer.innerHTML);
// 出力:
//
“`
element.remove()は、特に親要素が簡単に取得できない場合や、削除したい要素自身への参照を持っている場合に非常に便利です。ほとんどの場合、removeChildよりもremoveを使用するのが推奨されます。
要素削除のまとめ:
- 親要素から子要素を削除:
parentElement.removeChild(childElement) - 要素自身を削除:
element.remove()(推奨)
これで、HTML要素を自在に変更、追加、削除できるようになりました。これらの操作を、ユーザーのクリックなどのイベントに応じて実行する方法を見ていきましょう。
第7章:イベント処理と動的な変更の連動
JavaScriptによるHTML要素の動的な変更は、多くの場合、ユーザーの操作や特定の状況(ページの読み込み完了など)をトリガーとして実行されます。このトリガーとなるのが「イベント」です。JavaScriptは、要素に対してイベントが発生した際に特定の関数(イベントハンドラー、またはイベントリスナー)を実行するように設定できます。
7.1 イベントとは何か
イベントとは、ウェブページ上で発生する様々な出来事のことです。例:
- UIイベント: クリック (
click), マウスオーバー (mouseover), キー入力 (keydown,keyup), フォーム送信 (submit), フォーカス (focus,blur), スクロール (scroll), リサイズ (resize) など - 読み込みイベント: ページの読み込み完了 (
load), DOMツリー構築完了 (DOMContentLoaded), 画像の読み込み完了 (load) など - メディアイベント: 動画の再生 (
play), 一時停止 (pause), 再生終了 (ended) など
JavaScriptは、これらのイベントが発生した際に、事前に登録しておいた処理を実行できます。
7.2 イベントリスナーの登録: addEventListener()
要素にイベントリスナーを登録する最も推奨される方法です。一つの要素に対して同じ種類のイベントリスナーを複数登録できます。
- メソッド:
element.addEventListener(type, listener, options) - 引数:
type: 監視したいイベントの種類の文字列(例:'click','mouseover','submit')。イベント名の前に'on'は不要です。listener: イベントが発生した際に実行したい関数。この関数は、イベントに関する情報を含むEventオブジェクトを引数として受け取ります。options(省略可): イベントリスナーのオプションを指定するオブジェクト。よく使われるのはcapture(イベント伝播のどの段階で処理するか) やonce(一度実行されたら自動的に解除するか) 、passive(preventDefault()を呼ばないことを保証するか) などです。通常は省略します。
使用例:
“`html
まだクリックされていません。
“`
“`javascript
const myButtonEvent = document.getElementById(‘my-button-event’);
const statusParagraph = document.getElementById(‘status-paragraph’);
// ボタンにクリックイベントリスナーを登録
myButtonEvent.addEventListener(‘click’, function() {
// ボタンがクリックされたときに実行される関数
statusParagraph.textContent = ‘ボタンがクリックされました!’; // p要素の内容を変更
myButtonEvent.style.backgroundColor = ‘yellow’; // ボタンの背景色を変更
});
// 別途、別のクリックイベントリスナーを登録することも可能
myButtonEvent.addEventListener(‘click’, function() {
console.log(‘ボタンが二回クリックされたことになります (同じクリックで)’);
});
“`
複数のリスナーを登録できるため、機能ごとにイベント処理を分けて記述することが可能です。
7.3 イベントハンドラープロパティ (古い方法)
要素にはonclick, onmouseoverなどのイベントハンドラープロパティも存在します。
- プロパティ:
element.onclick = function() { ... }
使用例:
javascript
// 上記の例で
// myButtonEvent.onclick = function() {
// statusParagraph.textContent = 'ボタンがクリックされました! (onclick)';
// };
// myButtonEvent.onclick = function() { // これを記述すると上の処理は上書きされる
// console.log('こちらの処理だけが実行されます。');
// };
この方法の欠点は、一つのイベントタイプに対して一つのハンドラーしか設定できないことです。新しいハンドラーを設定すると、以前設定したハンドラーは上書きされてしまいます。そのため、特別な理由がない限りaddEventListenerを使用することが推奨されます。
7.4 イベントオブジェクト
イベントリスナー関数には、イベントに関する情報を含むEventオブジェクトが引数として渡されます。
Eventオブジェクトのよく使うプロパティ/メソッド:
event.type: 発生したイベントの種類(例:'click')。event.target: イベントが最初に発生した要素(イベント伝播のターゲット)。event.currentTarget: イベントリスナーが登録されている要素。イベント伝播の際に役立ちます。event.preventDefault(): イベントに対するブラウザのデフォルトの動作をキャンセルします(例:<a>タグのクリックによるページ遷移を阻止、フォームの送信を阻止)。event.stopPropagation(): イベントの伝播(バブリング/キャプチャリング)を停止します。
使用例:
“`html
リンク
“`
“`javascript
const myLinkEvent = document.getElementById(‘my-link-event’);
const myForm = document.getElementById(‘my-form’);
// リンクのデフォルト動作(ページ遷移)を阻止
myLinkEvent.addEventListener(‘click’, function(event) {
console.log(‘リンクがクリックされましたが、ページ遷移はしません。’);
event.preventDefault(); // これがないとリンク先に飛んでしまう
console.log(‘イベントの種類:’, event.type); // click
console.log(‘イベントターゲット:’, event.target); //
console.log(‘カレントターゲット:’, event.currentTarget); //
});
// フォーム送信のデフォルト動作を阻止
myForm.addEventListener(‘submit’, function(event) {
console.log(‘フォームが送信されようとしています。’);
event.preventDefault(); // これがないとページがリロードされる
console.log(‘送信を阻止しました。Ajaxなどで非同期送信できます。’);
// ここで入力値を取得したり、非同期処理を行う
const inputText = myForm.querySelector('input[type="text"]').value;
console.log('入力値:', inputText);
// 例えば、入力値をp要素に表示するなど動的な変更を行う
const resultParagraph = document.createElement('p');
resultParagraph.textContent = `入力された値: ${inputText}`;
myForm.appendChild(resultParagraph); // フォーム内に結果を追加
});
“`
イベントオブジェクトを使うことで、イベント発生時の詳細な情報を得たり、ブラウザのデフォルト動作を制御したりしながら、要素の動的な変更を行うことができます。
7.5 イベントリスナーの解除: removeEventListener()
不要になったイベントリスナーは解除できます。メモリリークを防ぐためにも重要です。
- メソッド:
element.removeEventListener(type, listener, options) - 引数:
addEventListenerに渡した全く同じイベントタイプ、リスナー関数、オプション。
重要な注意点: removeEventListenerでリスナーを解除するには、addEventListenerで登録した際に関数そのものを参照している必要があります。無名関数(匿名関数)でリスナーを登録した場合、同じ参照を得ることが難しいため解除が困難になります。解除する可能性があるリスナーは、名前付き関数として定義するか、変数に関数を代入しておきましょう。
使用例:
html
<button id="one-time-button">一度だけクリック有効</button>
“`javascript
const oneTimeButton = document.getElementById(‘one-time-button’);
// クリックされたときに実行する関数を定義
function handleClickOnce() {
console.log(‘ボタンがクリックされました!このリスナーは解除されます。’);
oneTimeButton.textContent = ‘クリックされました’;
// クリックされた後にこのリスナーを解除
oneTimeButton.removeEventListener(‘click’, handleClickOnce);
}
// ボタンにリスナーを登録
oneTimeButton.addEventListener(‘click’, handleClickOnce);
// あるいは、addEventListenerのoptionsでonce: trueを指定する方が簡単
// oneTimeButton.addEventListener(‘click’, function() {
// console.log(‘一度だけ実行されます’);
// oneTimeButton.textContent = ‘一度だけ実行されました’;
// }, { once: true });
“`
7.6 DOM構築完了を待つ: DOMContentLoaded イベント
JavaScriptコードがHTML要素を操作する場合、そのHTML要素がブラウザによって読み込まれ、DOMツリーが構築されている必要があります。もしDOMの準備ができていない状態で要素を取得しようとすると、nullが返されたりエラーになったりします。
これを避けるためには、JavaScriptコードをDOMContentLoadedイベントが発生するまで待ってから実行するのが最も安全な方法です。このイベントは、ブラウザがHTMLの解析を完了し、DOMツリーが完全に構築された時点で発生します。CSSや画像の読み込み完了は待たれません。
“`javascript
// DOMツリーの構築が完了したら実行される
document.addEventListener(‘DOMContentLoaded’, function() {
console.log(‘DOMツリーの構築が完了しました!ここから要素操作できます。’);
// ここに、getElementByIdなどの要素取得や、イベントリスナー登録などのコードを記述する
const myElement = document.getElementById('some-element');
if (myElement) {
myElement.textContent = 'DOM準備OK!';
}
});
// (古いブラウザ向けや、CSSや画像も含めたページ全体の読み込み完了を待つ場合は ‘load’ イベントを使うこともありますが、
// 通常はDOMContentLoadedで十分です)
// window.addEventListener(‘load’, function() {
// console.log(‘ページ全体の読み込みが完了しました!’);
// });
“`
<script>タグをHTMLの<body>タグの末尾に配置することでも、スクリプトが実行される時点ではそれより上のHTMLは解析されているため、多くの場合DOMContentLoadedを待たずに要素を操作できます。しかし、<head>内など、HTMLの解析が完了する前にスクリプトが実行される可能性がある場所に<script>タグを配置する場合は、DOMContentLoadedイベントを待つのが必須となります。ベストプラクティスとしては、DOMContentLoadedイベント内でDOM操作コードを実行するか、<script defer>属性を使用するか、<script type="module">を使用するのが推奨されます。
イベント処理のまとめ:
- イベント処理の基本: イベントが発生した際に特定の関数を実行する。
- リスナー登録 (推奨):
element.addEventListener('eventtype', handlerFunction) - リスナー解除:
element.removeEventListener('eventtype', handlerFunction)(登録時と同じ関数参照が必要) - イベント情報: ハンドラー関数の引数として渡される
Eventオブジェクト (event.target,event.preventDefault()など)。 - DOM操作のタイミング:
DOMContentLoadedイベントを待ってからDOM操作コードを実行する (<script>タグの位置やdefer属性にもよる)。
これらのイベント処理の知識と、これまでに学んだ要素の取得、変更、作成、削除の方法を組み合わせることで、ユーザーの操作に応じた本格的な動的ウェブページを作成できるようになります。
第8章:実践的なパターンと考慮事項
ここまでに学んだ基本的なDOM操作のテクニックを、より実践的に活用するためのヒントや、開発時に考慮すべき点について解説します。
8.1 JavaScriptコードの構造化
DOM操作を行うJavaScriptコードが複雑になってくると、コードの管理が重要になります。
- 関数を使う: 関連する処理を関数にまとめましょう。例えば、「要素を表示する関数」「エラーメッセージを表示する関数」「フォームをリセットする関数」などです。これにより、コードの再利用性が高まり、可読性も向上します。
- イベントハンドラー内の処理を分ける: イベントリスナーの中には、イベント情報の取得やデフォルト動作の阻止など、イベント自体に関する処理と、実際のDOM操作などの処理が混在しがちです。イベントハンドラー関数内では、最低限のイベント処理だけを行い、実際の画面更新ロジックは別の関数として呼び出すようにすると、コードが整理されます。
“`javascript
// 悪い例: 処理がイベントハンドラー内にべったり書かれている
// myButton.addEventListener(‘click’, function(event) {
// event.preventDefault();
// const value = inputElement.value;
// if (value === ”) {
// errorElement.textContent = ‘入力してください!’;
// errorElement.style.display = ‘block’;
// } else {
// resultElement.textContent = ‘入力値: ‘ + value;
// errorElement.style.display = ‘none’;
// }
// });
// 良い例: 処理を関数に分割
function updateDisplay(value) {
const resultElement = document.getElementById(‘result’); // 必要ならここで要素取得
const errorElement = document.getElementById(‘error’);
if (value === '') {
errorElement.textContent = '入力してください!';
errorElement.style.display = 'block';
resultElement.textContent = ''; // エラー時は結果をクリア
} else {
resultElement.textContent = '入力値: ' + value;
errorElement.style.display = 'none'; // エラーメッセージを非表示
}
}
document.addEventListener(‘DOMContentLoaded’, function() {
const myButton = document.getElementById(‘my-button’);
const inputElement = document.getElementById(‘my-input’);
myButton.addEventListener('click', function(event) {
event.preventDefault(); // デフォルト動作の阻止はイベントハンドラーで
const inputValue = inputElement.value;
updateDisplay(inputValue); // 実際の処理は別関数を呼び出す
});
});
“`
8.2 CSSによるスタイルの管理
JavaScriptで要素のスタイルを直接操作する(element.style.backgroundColor = 'red';など)のは、一時的なアニメーションやごく簡単な変更に限定するのが望ましいです。
より複雑なスタイルや、状態に応じたスタイルの切り替え(例: アクティブなボタン、エラー状態の入力欄)は、CSSでクラスとして定義し、JavaScriptではclassListプロパティを使ってそのクラスを追加したり削除したりする方が、以下の理由から推奨されます。
- 関心の分離: HTMLは構造、CSSは見た目、JavaScriptは振る舞いと、それぞれの役割を明確に分けられます。
- 管理の容易さ: スタイルの変更はCSSファイルで行えるため、見た目の調整がしやすくなります。JavaScriptコードがスタイル情報で煩雑になるのを避けられます。
- 再利用性: 同じスタイルセットを複数の要素や異なる状況で簡単に適用できます。
- アニメーション: CSSトランジションやアニメーションは、クラスの追加・削除と組み合わせて簡単に実現できます。
“`css
/ CSSファイル /
.highlight {
background-color: yellow;
font-weight: bold;
}
.error {
color: red;
border: 1px solid red;
}
.hidden {
display: none;
}
“`
“`javascript
// JavaScript
const targetElement = document.getElementById(‘some-element’);
const inputElement = document.getElementById(‘input-field’);
// 要素にハイライトクラスを追加/削除
targetElement.classList.add(‘highlight’);
// …
targetElement.classList.remove(‘highlight’);
// 入力エラー時にエラークラスを適用/解除
if (inputElement.value === ”) {
inputElement.classList.add(‘error’);
} else {
inputElement.classList.remove(‘error’);
}
// 要素の表示/非表示を切り替え
const messageBox = document.getElementById(‘message-box’);
messageBox.classList.toggle(‘hidden’);
“`
8.3 パフォーマンスの考慮事項
DOM操作は、ブラウザにとって比較的にコストのかかる処理です。頻繁なDOM操作はページの応答性を低下させる可能性があります。以下の点を考慮しましょう。
- DOM操作の回数を減らす:
- 複数の要素を追加する場合、
DocumentFragmentを使うか、innerHTMLでまとめてHTML文字列を挿入する(ただしセキュリティに注意)。 - 要素のスタイルを複数変更する場合、複数の
element.style.propertyName = valueよりも、スタイルをまとめたCSSクラスをclassListで切り替える方が効率的な場合があります。
- 複数の要素を追加する場合、
- ライブコレクションの扱い:
getElementsByClassNameやgetElementsByTagNameのようなライブコレクションは、DOM変更のたびに更新されるため、ループ処理中にDOMを変更すると予期しない結果を招くことがあります。ループ処理などで一時的に固定したい場合は、Array.from(collection)などで配列に変換してから扱うと安全です。 - アニメーション: 滑らかなアニメーションが必要な場合は、DOMプロパティの直接変更よりも、CSSアニメーションやトランジションを活用するか、
requestAnimationFrameを使ったループ内で変更を行う方がパフォーマンスが向上します。 - 要素の再フロー/再ペイント: DOMの構造や要素のサイズ、位置などが変更されると、ブラウザはページのレイアウトを再計算し(リフロー/リフロー)、必要に応じて要素を描画し直します(リペイント)。これらの処理はコストがかかるため、連続して多数のDOM操作を行うと遅延の原因になります。DOM操作をバッチ化(まとめて行う)ことで、リフロー/リペイントの回数を減らすことができます。
8.4 エラーハンドリングと要素の存在確認
JavaScriptでDOM操作を行う際、対象となる要素がHTML上に存在しない場合、エラーが発生する可能性があります。特にgetElementByIdやquerySelectorは、該当する要素がない場合にnullを返します。
要素のプロパティやメソッドを呼び出す前に、その要素がnullでないか確認する習慣をつけましょう。
“`javascript
const maybeElement = document.getElementById(‘maybe-existent’);
if (maybeElement) {
// 要素が存在する場合のみ処理を実行
maybeElement.textContent = ‘要素が見つかりました。’;
} else {
// 要素が見つからなかった場合の処理
console.warn(‘指定されたIDの要素が見つかりませんでした。’);
}
// querySelectorAll などが返すコレクション/リストの場合も、
// length を確認したり、ループの中で要素が存在するか確認する
const items = document.querySelectorAll(‘.my-item’);
if (items.length > 0) {
items.forEach(item => {
// 各アイテムに対する処理
item.style.color = ‘blue’;
});
}
“`
また、非同期処理(Ajax通信のコールバックなど)の中でDOM操作を行う場合は、非同期処理が完了した時点でまだその要素がページ上に存在するかどうか(例えば、ユーザーがその間に別のページに遷移したなど)も考慮する必要があります。
8.5 フォーム要素の操作
<input>, <textarea>, <select>などのフォーム要素は、他の要素とは少し異なる操作方法があります。
- 値の取得/設定: 要素の
valueプロパティを使用します。inputElement.value = '新しい入力値';const currentValue = textareaElement.value;
- チェック状態: チェックボックスやラジオボタンは
checkedプロパティ(真偽値)で状態を取得/設定します。checkbox.checked = true;if (radioButton.checked) { ... }
- 選択状態:
<select>要素はvalueプロパティで選択中の<option>のvalue属性値を取得/設定できます。複数の選択肢がある場合は、selectedOptionsや個々の<option>要素のselectedプロパティを使用します。 - 無効化:
disabledプロパティ(真偽値)で要素を無効化/有効化できます。button.disabled = true;input.disabled = false;
使用例:
html
<input type="checkbox" id="my-checkbox">
<button id="toggle-button-disabled">有効/無効を切り替え</button>
“`javascript
const myCheckbox = document.getElementById(‘my-checkbox’);
const toggleDisabledButton = document.getElementById(‘toggle-button-disabled’);
// チェックボックスの状態に応じてボタンを有効/無効にする
myCheckbox.addEventListener(‘change’, function() {
toggleDisabledButton.disabled = !myCheckbox.checked;
});
// 初期状態を設定
toggleDisabledButton.disabled = !myCheckbox.checked; // ページ読み込み時の状態を反映
“`
フォーム要素はユーザーからの入力を扱う上で非常に重要であり、これらのプロパティを使ってその状態を動的に制御します。
第9章:更なる学習リソースと次のステップ
本記事では、JavaScriptによるDOM操作の基本的な手法を詳細に解説しました。しかし、DOM APIは非常に広範であり、さらに学ぶべきことや、より高度なテクニックが存在します。
9.1 より高度なDOM API
- ノードのクローン:
element.cloneNode(deep)(要素とその子孫を複製する) - スタイルの詳細情報:
window.getComputedStyle(element)(要素に最終的に適用されている全てのスタイルを取得する) - 要素の位置やサイズ:
element.getBoundingClientRect()(要素のビューポートに対する位置とサイズを取得する) - 属性の変化を監視:
MutationObserver(DOMツリーの変更を非同期に監視する)
これらのAPIを使うことで、より洗練された動的なウェブページを作成できます。
9.2 DOM操作ライブラリとフレームワーク
生のDOM APIは強力ですが、特に複雑なアプリケーションではコードが冗長になりがちです。このような課題を解決するために、DOM操作を抽象化・簡略化するライブラリや、そもそもDOM操作の考え方を変えるフレームワークが登場しました。
- jQuery: 過去に非常によく使われたライブラリで、CSSセレクターを使った要素選択やAjax通信などを簡単な構文で記述できます。現在でも多くのレガシープロジェクトで使われていますが、モダンなJavaScriptの機能向上により、新規プロジェクトで必須とされることは減っています。
- React, Vue, Angular: これらのモダンなJavaScriptフレームワークは、開発者が直接DOMを操作するのではなく、「コンポーネント」という単位でUIを構築し、データの変更に応じてフレームワークが効率的にDOMを更新する(仮想DOMなどを使用)というアプローチをとります。これにより、複雑なUI開発や状態管理が容易になります。モダンなウェブ開発を学ぶ上で、これらのフレームワークは避けて通れない道となっています。
もしあなたが本格的なウェブアプリケーション開発を目指すのであれば、生のDOM操作の基本をしっかりと理解した上で、これらのフレームワークの世界に足を踏み入れることをおすすめします。
9.3 セキュリティ(XSS)について改めて
innerHTMLやinsertAdjacentHTMLを使う際のクロスサイトスクリプティング(XSS)のリスクについて触れましたが、これはウェブ開発において非常に重要な問題です。ユーザーからの入力など、信頼できないソースからの文字列をDOMに挿入する場合は、常に注意が必要です。
- 可能な限り
textContentやcreateElement+appendChildのような、テキストや要素を安全に扱うAPIを使う。 innerHTMLなどを使う場合は、必ずサニタイズライブラリ(DOMPurifyなど)を使用して、悪意のあるスクリプトを除去する処理を施す。- サーバーサイドでも適切なサニタイズやエスケープ処理を行う。
セキュリティ対策は、動的なウェブページを作成する上で常に念頭に置くべき責任です。
結論
本記事では、JavaScriptを使ってHTML要素を動的に変更するための基本的な概念から具体的な方法までを、詳細に解説しました。
DOM(Document Object Model)は、ウェブページの構造をツリーとして表現するモデルであり、JavaScriptはdocumentオブジェクトを介してこのDOMにアクセスし、要素を操作します。
要素の操作には、まず操作したい要素をDOMツリーの中から「取得」する必要があります。getElementById, getElementsByClassName, getElementsByTagName, querySelector, querySelectorAllといった多様なメソッドがあり、目的に応じて使い分けることが重要です。特にHTMLCollectionとNodeListのライブ/スタティックな違いは理解しておくとバグを防げます。
要素が取得できたら、その「内容」をtextContent(安全なテキスト変更)やinnerHTML(HTML構造変更、セキュリティ注意)で変更したり、「属性」をgetAttribute, setAttribute, removeAttribute、あるいはclassList, element.styleといったプロパティで操作したりできます。
また、新しい要素をdocument.createElementで「作成」し、appendChild, insertBefore, insertAdjacentElement, append, prepend, before, afterといったメソッドを使ってDOMツリー内の任意の位置に「挿入」することも可能です。不要になった要素はremoveChildやremoveで「削除」できます。
これらのDOM操作は、ユーザーのクリックやキー入力などの「イベント」をトリガーとして実行されることが一般的です。addEventListenerを使ってイベントリスナーを登録し、イベントオブジェクトから情報を得ながら、動的にDOMを操作します。DOM操作はDOMContentLoadedイベント以降に行うことで、要素が存在しないエラーを防ぎます。
実践的な開発では、コードを関数で構造化し、スタイルの管理はCSSクラスに任せる、DOM操作のパフォーマンスを考慮するといった点も重要になります。また、フォーム要素には特有の操作プロパティがあること、要素の存在確認やセキュリティ対策が不可欠であることも学びました。
JavaScriptによるDOM操作は、現代のインタラクティブなウェブサイトやウェブアプリケーション開発における最も基本的なスキルの一つです。ここで学んだ知識は、ReactやVueのようなモダンなフレームワークを学ぶ上でも土台となります。
ぜひ、実際にコードを書いて動かしながら、これらの概念と手法を習得してください。ボタンをクリックしたらテキストが変わる、入力欄に何か入れたら画像が表示される、といった小さな機能から作り始めて、徐々に複雑なインタラクションに挑戦していきましょう。
ウェブページを動的に変化させる力は、あなたのアイデア次第で無限の可能性を秘めています。この知識を活かして、ユーザーを驚かせ、楽しませるような素晴らしいウェブ体験を創造してください。
これで、JavaScriptによるHTML要素の動的変更の基本に関する詳細な記事を終わります。最後までお読みいただき、ありがとうございました。