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

2025-07-28

はじめに

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

要素の取得方法

従来の取得方法

// 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)');

取得方法の比較

方法戻り値LiveかStaticかパフォーマンス
getElementById単一要素最速
getElementsByClassNameHTMLCollectionLive速い
getElementsByTagNameHTMLCollectionLive速い
querySelector単一要素やや遅い
querySelectorAllNodeListStatic最も遅い

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

要素の内容と属性の変更

内容の変更

const div = document.querySelector('.content');

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

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

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

属性の操作

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)

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');

スタイルの変更

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

新しい要素の作成と追加

要素の作成

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

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

要素の追加方法

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の位置指定

位置説明
‘beforebegin’要素の直前に挿入
‘afterbegin’要素の最初の子として挿入
‘beforeend’要素の最後の子として挿入
‘afterend’要素の直後に挿入

要素の削除

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 = '';

DocumentFragmentによる効率的な操作

複数の要素を追加する場合、DocumentFragmentを使用するとパフォーマンスが向上します。

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への再フロー(再描画)が1回で済む
  • メモリ効率が良い
  • 複雑な構造をオフスクリーンで構築可能

テンプレート要素の活用

HTML5の<template>タグを使用すると、複雑な構造を効率的に扱えます。

<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);

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

// リストに項目を追加する関数
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アクセラレーション可能なプロパティを利用

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

  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;
  }
}