JavaScriptによるDOM操作:要素の取得・変更・追加・削除

2025-07-28

はじめに

JavaScriptのDOM操作は現代のフロントエンド開発において核心的なスキルです。この記事では、DOM要素の取得方法、内容や属性の変更、新しい要素の追加と削除など、実践的なDOM操作テクニックを詳細に解説します。jQueryなどのライブラリが普及している今日でも、これらのネイティブDOM APIを理解することは非常に重要です。

要素の取得方法

JavaScriptでDOM要素を取得する方法は、大きく分けて従来のやり方モダンなやり方があります。それぞれ特徴があります。

従来の取得方法

// IDで要素を取得
const header = document.getElementById('header');

// クラス名で要素を取得(最初の1つのみ)
const firstItem = document.getElementsByClassName('item')[0];

// タグ名で要素を取得(HTMLCollectionを返す)
const paragraphs = document.getElementsByTagName('p');

モダンな取得方法(querySelector系)

// 単一の要素を取得(最初にマッチした要素)
const button = document.querySelector('.btn-primary');

// 複数の要素を取得(NodeListを返す)
const allButtons = document.querySelectorAll('button');

// 複雑なセレクタも可能
const specialLinks = document.querySelectorAll('nav a:not(.external)');

取得方法の比較

  • 高速・単純 → 従来のgetElementByIdなど
  • 柔軟・読みやすい → モダンなquerySelector
方法戻り値LiveかStaticかパフォーマンス
getElementById単一要素最速
getElementsByClassNameHTMLCollectionLive速い
getElementsByTagNameHTMLCollectionLive速い
querySelector単一要素やや遅い
querySelectorAllNodeListStatic最も遅い

Liveコレクション:DOMの変更に伴って自動的に更新される
Staticコレクション:取得時のスナップショットで変更されない

現在はモダンな方法が推奨されることが多いが、パフォーマンス重視の場面やID取得のみなら従来メソッドも有効。

要素の内容と属性の変更

内容の変更

DOM要素の中身(テキストやHTML)を変更するには、主に以下のプロパティを使います。

  • textContent:要素内のテキストのみを取得・設定します。HTMLタグは無視されるので安全にテキストを扱いたいときに使います。
  • innerHTML:要素内のHTMLを文字列として取得・設定できます。タグを含むHTMLの挿入や書き換えに便利ですが、外部からの入力を直接設定するとセキュリティリスク(XSS)になるので注意が必要です。
const div = document.querySelector('.content');

// textContent(推奨) - テキストのみ安全に設定
div.textContent = '新しいテキスト内容';

// innerHTML - HTML文字列をパースして設定(XSSの危険あり)
div.innerHTML = '太字のテキスト';

// innerText - レンダリングされたテキストを取得/設定(パフォーマンス低い)
div.innerText = '表示されるテキスト';

属性の操作

HTML要素の属性(例えば srchrefalt など)は、以下のメソッドやプロパティで操作します。

  • getAttribute(name):指定した属性の値を取得します。
  • setAttribute(name, value):指定した属性の値を設定または追加します。
  • removeAttribute(name):指定した属性を削除します。
    これらは属性名に柔軟に対応でき、カスタム属性(data-*)の操作にも使えます。
const image = document.querySelector('img');

// 属性の設定
image.setAttribute('alt', '説明テキスト');
image.setAttribute('data-info', '123');

// 属性の取得
const altText = image.getAttribute('alt');

// 属性の削除
image.removeAttribute('data-info');

// プロパティとして直接アクセス(特定の属性)
image.src = 'new-image.jpg';
image.className = 'new-class'; // classはclassName

classの操作(classList API)

要素のCSSクラスは、classListプロパティを使うと便利です。これはクラス名の集合を扱うオブジェクトで、次のようなメソッドがあります。

  • add(className):クラスを追加
  • remove(className):クラスを削除
  • toggle(className):クラスがあれば削除、なければ追加
  • contains(className):クラスの有無を確認
    classListは複数クラスの操作も簡単かつ高速で、従来の文字列操作より安全で扱いやすいです。
const element = document.getElementById('myElement');

// クラスの追加
element.classList.add('active', 'highlight');

// クラスの削除
element.classList.remove('inactive');

// クラスのトグル(存在すれば削除、なければ追加)
element.classList.toggle('visible');

// クラスの存在確認
if (element.classList.contains('hidden')) {
    console.log('要素は非表示です');
}

// クラスの置換
element.classList.replace('old-class', 'new-class');

スタイルの変更

1. styleプロパティを使う方法

特定のCSSプロパティを直接指定して変更します。プロパティ名はキャメルケース(例: backgroundColor)で書きます。

2. classの追加・削除で切り替える方法

あらかじめCSSファイルでスタイルを用意し、JavaScriptからclassListでクラスを追加・削除することでスタイルを切り替えます。

3. setAttributeでstyle属性を書き換える方法

style属性全体を文字列として操作できますが、細かい制御にはあまり使われません。

const box = document.querySelector('.box');

// スタイルの個別設定(キャメルケース)
box.style.backgroundColor = 'blue';
box.style.width = '100px';
box.style.marginTop = '20px';

// cssTextで一括設定(セミコロン区切り)
box.style.cssText = 'color: red; font-size: 16px; border: 1px solid black;';

// スタイルの削除
box.style.width = '';

注意点

  • styleプロパティで設定したスタイルはインラインスタイルとして適用される
  • クラスを追加/削除する方法がより推奨される

新しい要素の作成と追加

要素の作成

document.createElement() メソッドを使って、新しいタグ(要素)を作成します。まだページには存在せず、メモリ上のオブジェクトです。

// 新しい要素を作成
const newDiv = document.createElement('div');
newDiv.textContent = '新しいdiv要素';

// 属性やクラスを設定
newDiv.className = 'box highlight';
newDiv.id = 'newBox';

要素の追加方法

作成した要素を既存のDOMツリーに組み込むには、親要素のメソッドを使います。代表的なものは以下です。

  • appendChild(child):親要素の最後に子要素を追加
  • insertBefore(newNode, referenceNode):指定した要素の前に新しい要素を挿入
  • append():複数のノードや文字列も追加可能(最近のブラウザでサポート)
const container = document.querySelector('.container');

// 子要素として末尾に追加
container.appendChild(newDiv);

// 特定の位置に挿入(参照要素の前に)
const firstChild = container.firstElementChild;
container.insertBefore(newDiv, firstChild);

// insertAdjacentHTML - HTML文字列を直接挿入
container.insertAdjacentHTML('beforeend', '

新しい段落

'); // insertAdjacentElement - 要素オブジェクトを挿入 container.insertAdjacentElement('afterbegin', newDiv); // insertAdjacentText - テキストノードを挿入 container.insertAdjacentText('beforeend', '追加テキスト');

insertAdjacentHTMLの位置指定

insertAdjacentHTMLメソッドは、以下の4つの位置を指定できます。

位置説明
‘beforebegin’要素の直前に挿入
‘afterbegin’要素の最初の子として挿入
‘beforeend’要素の最後の子として挿入
‘afterend’要素の直後に挿入
  • HTML文字列を直接挿入するため、要素の作成と追加が一度にできる
  • innerHTMLの書き換えよりパフォーマンスが良く、既存の子要素を壊さない
  • 挿入位置を柔軟に指定できるので部分的なDOM更新に便利

要素の削除

JavaScriptでDOM要素を削除する方法は主に次の2つがあります。

1. 親要素のremoveChild()を使う方法

削除したい要素の親要素から呼び出し、対象の子要素を指定して削除します。

2. 要素自身のremove()を使う方法(モダンブラウザ対応)

削除したい要素自身に対してremove()を呼ぶと、自分をDOMツリーから取り除きます。

const elementToRemove = document.querySelector('.old-element');

// 親要素から削除
elementToRemove.parentNode.removeChild(elementToRemove);

// よりモダンな方法(IE11以降)
elementToRemove.remove();

// すべての子要素を削除
const list = document.getElementById('myList');
while (list.firstChild) {
    list.removeChild(list.firstChild);
}

// 代替方法(高速)
list.innerHTML = '';
// または
list.textContent = '';
  • remove()はコードがシンプルでわかりやすく、最近のブラウザで広くサポートされています。
  • 古い環境ではremoveChild()が確実ですが、親要素を取得する手間があります。
  • 削除後、その要素はメモリから解放されるわけではないので、参照を残さないよう注意しましょう。

DocumentFragmentによる効率的な操作

複数の要素を追加する場合、DocumentFragmentを使用するとパフォーマンスが向上します。DocumentFragment(ドキュメントフラグメント)は、軽量な仮のDOMコンテナで、実際のページには表示されません。

const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `項目 ${i}`;
    fragment.appendChild(li);
}

document.getElementById('list').appendChild(fragment);
  • 複数の要素をまとめて一時的に保持できる
  • フラグメント内で操作(要素の追加・削除)を行い、最後にまとめてページのDOMツリーに挿入できる
  • パフォーマンス向上に役立つ(複数のDOM操作を一度の挿入で済ませるため、再描画の回数を減らせる)

利点:DocumentFragmentは、複数の要素を効率よくまとめて処理し、ブラウザの負荷を減らすための便利な仕組みです。大量の要素を動的に追加するときに特に有効です。

テンプレート要素の活用

HTML5の<template>タグを使用すると、複雑な構造を効率的に扱えます。<template>タグを使う利点は、HTMLの一部をページに表示せずにあらかじめ用意し、必要なときにJavaScriptで安全かつ効率的に複製して挿入できる点にあります。

<template id="productTemplate">
    <div class="product">
        <h3 class="title"></h3>
        <p class="price"></p>
        <button class="add-to-cart">カートに追加</button>
    </div>
</template>
const template = document.getElementById('productTemplate');
const clone = template.content.cloneNode(true);

clone.querySelector('.title').textContent = '新しい商品';
clone.querySelector('.price').textContent = '¥1,980';

document.getElementById('products').appendChild(clone);

<template>タグの主な利点

  • 非表示で構造を保持
    <template>内の内容はブラウザのレンダリング対象外で、画面に表示されません。だから初期表示を軽く保てます。
  • 複数回の再利用が容易
    JavaScriptでcontentプロパティから中身を複製(cloneNode(true))できるため、同じ構造の要素を何度でも生成可能。
  • HTMLとして記述可能
    文字列で生成するのではなく、通常のHTMLの形でテンプレートを書けるので可読性が高い。
  • セキュリティ面でも安全
    テンプレート内のスクリプトは実行されず、XSSリスクを抑えられる。

<template>は動的UI生成に最適で、手軽に繰り返し使う部品を管理できる便利なHTML要素です。特に複雑なリストやカード表示の作成時に重宝します。

実践的な例:動的なリスト操作

// リストに項目を追加する関数
function addListItem(text) {
    const list = document.getElementById('itemList');
    const newItem = document.createElement('li');
    newItem.className = 'list-item';
    newItem.innerHTML = `
        ${text}
        
    `;

    // 削除ボタンのイベントリスナー
    newItem.querySelector('.delete-btn').addEventListener('click', function() {
        newItem.remove();
    });

    list.appendChild(newItem);
}

// フォーム送信時の処理
document.getElementById('addForm').addEventListener('submit', function(e) {
    e.preventDefault();
    const input = document.getElementById('itemInput');
    if (input.value.trim()) {
        addListItem(input.value.trim());
        input.value = '';
    }
});

パフォーマンスのベストプラクティス

  1. DOMアクセスの最小化:同じ要素を繰り返し取得しない
  2. 一括操作:DocumentFragmentやinnerHTMLを使用
  3. レイアウトスラッシングの回避:読み取りと書き込みを分離
   // 悪い例(読み取りと書き込みが交互)
   for (let i = 0; i < boxes.length; i++) {
       boxes[i].style.width = boxes[i].offsetWidth + 10 + 'px';
   }

   // 良い例(読み取りを先に)
   const widths = [];
   for (let i = 0; i < boxes.length; i++) {
       widths.push(boxes[i].offsetWidth);
   }

   for (let i = 0; i < boxes.length; i++) {
       boxes[i].style.width = widths[i] + 10 + 'px';
   }
  1. アニメーションにはCSSを使用:transformやopacityなどGPUアクセラレーション可能なプロパティを利用

クロスブラウザ対応の注意点

クロスブラウザ対応とは、Webサイトやアプリケーションがどのブラウザでも正しく動作し、見た目が崩れないように工夫することを指します。ブラウザごとにJavaScriptやCSSの対応状況が異なるため、新しい機能を使う際には、その機能がすべての主要ブラウザでサポートされているか確認することが重要です。また、ブラウザによってフォントサイズや余白、レイアウトの扱いに微妙な違いがあるため、実際の画面で動作や表示を確認し、必要に応じて調整を行う必要があります。CSSでは、一部のスタイルにブラウザ固有の接頭辞(例えば「-webkit-」)が必要な場合もあるため、これを忘れると意図した効果が得られません。さらに、新しい機能を使う場合でも、古いブラウザ向けに似た動きをする代替コードやスタイルを用意するなどの工夫が求められます。こうしたクロスブラウザ対応は、どの環境でも快適に使えるWebを作るための基本であり、初心者は主要なブラウザでの動作確認を習慣づけることが大切です。

  1. classListはIE10以降でサポート
  2. remove()メソッドはIE11以降でサポート
  3. insertAdjacentHTMLは広くサポートされている
  4. 古いIE対応が必要な場合は、polyfillの使用を検討

まとめ

DOM操作はJavaScriptの核心的な機能であり、要素の取得・変更・追加・削除はその基本です。querySelector系のメソッドを使えばCSSセレクタと同じ感覚で要素を選択でき、classList APIを使えばクラス操作が簡単になります。新しい要素の追加にはappendChildやinsertAdjacentHTMLが便利で、DocumentFragmentを使えばパフォーマンスを向上させられます。

重要なポイントは:

  • DOM操作はコストが高いので効率的に行う
  • innerHTMLにはXSSのリスクがあることに注意
  • 可能な限りクラス操作でスタイルを変更する
  • 複雑な構造にはテンプレート要素を活用する
  • パフォーマンスを意識したコーディングを心がける

これらのDOM操作技術をマスターすることで、動的でインタラクティブなウェブアプリケーションを構築する基礎が身につきます。

演習問題

初級問題(3問)

問題1

HTMLページ内のIDが"title"の要素を取得し、そのテキスト内容を「JavaScript学習」に変更するコードを書いてください。

問題2

クラス名が"item"のすべての要素を取得し、それらの背景色を黄色に変更するコードを書いてください。

問題3

新しい<li>要素を作成し、IDが"list"の<ul>要素の最後に追加するコードを書いてください。追加する<li>のテキストは「新しいアイテム」とします。

中級問題(6問)

問題4

ボタンクリック時に、IDが"container"の要素内のすべての子要素を削除する関数を作成してください。

問題5

フォームの入力値が変更されるたびに、IDが"output"の要素にその値を表示するイベントリスナーを実装してください(入力要素のIDは"userInput"とします)。

問題6

テーブル(IDが"dataTable")に行を動的に追加する関数を作成してください。追加する行のデータは配列で受け取るものとします。

問題7

要素のクラスをトグル(追加/削除を切り替え)する関数を作成してください。要素のIDとトグルしたいクラス名を引数で受け取るようにします。

問題8

ページ内のすべてのリンク(<a>タグ)を取得し、外部リンク(hrefがhttpで始まる)のみターゲットを"_blank"に設定するコードを書いてください。

問題9

要素をドラッグ可能にするコードを書いてください。IDが"draggable"の要素をマウスでドラッグして移動できるようにします。

上級問題(3問)

問題10

指定した親要素の子要素をすべて深く複製(クローン)し、複製した要素内のテキストノードの内容を逆順にする関数を作成してください。

問題11

マウス座標に追従するカスタムツールチップを作成してください。ホバーした要素のdata-tooltip属性の値をツールチップ内容として表示します。

問題12

無限スクロール機能を実装してください。ページ最下部に到達したら新しいコンテンツを動的に読み込み表示します。

解答例

初級解答

解答1

document.getElementById('title').textContent = 'JavaScript学習';

解答2

const items = document.getElementsByClassName('item');
for (let item of items) {
  item.style.backgroundColor = 'yellow';
}

解答3

const newItem = document.createElement('li');
newItem.textContent = '新しいアイテム';
document.getElementById('list').appendChild(newItem);

中級解答

解答4

function clearContainer() {
  const container = document.getElementById('container');
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
}

解答5

document.getElementById('userInput').addEventListener('input', function(e) {
  document.getElementById('output').textContent = e.target.value;
});

解答6

function addTableRow(data) {
  const table = document.getElementById('dataTable');
  const row = table.insertRow();

  data.forEach(item => {
    const cell = row.insertCell();
    cell.textContent = item;
  });
}

解答7

function toggleClass(elementId, className) {
  const element = document.getElementById(elementId);
  element.classList.toggle(className);
}

解答8

const links = document.getElementsByTagName('a');
for (let link of links) {
  if (link.href.startsWith('http')) {
    link.target = '_blank';
  }
}

解答9

const draggable = document.getElementById('draggable');
let isDragging = false;
let offsetX, offsetY;

draggable.addEventListener('mousedown', (e) => {
  isDragging = true;
  offsetX = e.clientX - draggable.getBoundingClientRect().left;
  offsetY = e.clientY - draggable.getBoundingClientRect().top;
  draggable.style.cursor = 'grabbing';
});

document.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  draggable.style.left = `${e.clientX - offsetX}px`;
  draggable.style.top = `${e.clientY - offsetY}px`;
  draggable.style.position = 'absolute';
});

document.addEventListener('mouseup', () => {
  isDragging = false;
  draggable.style.cursor = 'grab';
});

上級解答

解答10

function cloneAndReverseText(parentId) {
  const parent = document.getElementById(parentId);
  const clone = parent.cloneNode(true);

  const textNodes = [];
  const walker = document.createTreeWalker(clone, NodeFilter.SHOW_TEXT);

  let node;
  while (node = walker.nextNode()) {
    textNodes.push(node);
  }

  textNodes.forEach(node => {
    node.nodeValue = node.nodeValue.split('').reverse().join('');
  });

  return clone;
}

解答11

const tooltip = document.createElement('div');
tooltip.style.position = 'absolute';
tooltip.style.display = 'none';
tooltip.style.backgroundColor = '#333';
tooltip.style.color = '#fff';
tooltip.style.padding = '5px 10px';
tooltip.style.borderRadius = '4px';
document.body.appendChild(tooltip);

document.querySelectorAll('[data-tooltip]').forEach(element => {
  element.addEventListener('mouseenter', (e) => {
    tooltip.textContent = e.target.dataset.tooltip;
    tooltip.style.display = 'block';
  });

  element.addEventListener('mousemove', (e) => {
    tooltip.style.left = `${e.clientX + 10}px`;
    tooltip.style.top = `${e.clientY + 10}px`;
  });

  element.addEventListener('mouseleave', () => {
    tooltip.style.display = 'none';
  });
});

解答12

let isLoading = false;
let page = 1;

window.addEventListener('scroll', () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;

  if (scrollTop + clientHeight >= scrollHeight - 100 && !isLoading) {
    isLoading = true;
    loadMoreContent();
  }
});

async function loadMoreContent() {
  const loader = document.createElement('div');
  loader.textContent = '読み込み中...';
  document.body.appendChild(loader);

  try {
    const response = await fetch(`https://api.example.com/items?page=${page}`);
    const data = await response.json();

    data.items.forEach(item => {
      const element = document.createElement('div');
      element.className = 'item';
      element.textContent = item.content;
      document.body.appendChild(element);
    });

    page++;
  } catch (error) {
    console.error('読み込みエラー:', error);
  } finally {
    document.body.removeChild(loader);
    isLoading = false;
  }
}