T-CREATOR

【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する

【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する

Web アプリケーションを開発していると、動的に変化する DOM を適切に監視したい場面に何度も遭遇しますよね。従来は setInterval やイベントリスナーで無理やり対応していたものの、パフォーマンスの問題や検知漏れに悩まされた経験をお持ちの方も多いのではないでしょうか。

そこで今回は、JavaScript が提供する強力な監視 API である MutationObserverResizeObserver の活用方法を徹底解説します。これらの API を使いこなせば、DOM の変化を確実かつ効率的に捉えることができるようになるでしょう。

MutationObserver & ResizeObserver 早見表

Observer 種類別比較表

#Observer 種類監視対象主な用途パフォーマンス影響
1MutationObserverDOM の構造変化、属性変化、テキスト変化動的コンテンツの変更検知、DOM 操作の追跡低(非同期実行)
2ResizeObserver要素のサイズ変化レスポンシブ対応、レイアウト調整、コンテナクエリ的実装低(requestAnimationFrame ベース)

MutationObserver 設定オプション早見表

#オプション名説明デフォルト値
1childListboolean子ノードの追加・削除を監視false
2attributesboolean属性の変更を監視false
3characterDatabooleanテキストノードの変更を監視false
4subtreeboolean子孫ノード全体を監視false
5attributeOldValueboolean属性の古い値を記録false
6characterDataOldValuebooleanテキストの古い値を記録false
7attributeFilterstring[]監視する属性名を指定undefined

ResizeObserver コールバック情報早見表

#プロパティ名説明
1contentRectDOMRectReadOnly要素のコンテンツ領域のサイズと位置(非推奨)
2borderBoxSizeResizeObserverSize[]border-box のサイズ(border + padding + content)
3contentBoxSizeResizeObserverSize[]content-box のサイズ(content のみ)
4devicePixelContentBoxSizeResizeObserverSize[]デバイスピクセル単位のサイズ
5targetElement監視対象の要素

背景

DOM 監視の歴史と課題

Web アプリケーションの高度化に伴い、DOM の動的な変更を検知する需要が増大してきました。シングルページアプリケーション(SPA)の普及、サードパーティ製ウィジェットの埋め込み、ユーザー操作に応じた UI の動的変更など、現代の Web 開発では DOM 監視が欠かせません。

従来は以下のような方法で DOM の変化を監視していました。

javascript// 従来の方法:setInterval による定期チェック
let previousHeight = element.offsetHeight;

setInterval(() => {
  const currentHeight = element.offsetHeight;
  if (currentHeight !== previousHeight) {
    console.log('高さが変わりました');
    previousHeight = currentHeight;
  }
}, 100); // 100ms ごとにチェック

この方法にはいくつかの問題点があります。

javascript// 問題1:不要なチェックによる CPU 負荷
// 変更がなくても 100ms ごとに処理が実行される

// 問題2:検知タイミングの遅延
// 最大 100ms の遅延が発生する可能性がある

// 問題3:複数箇所で監視すると処理が重複
const interval1 = setInterval(checkElement1, 100);
const interval2 = setInterval(checkElement2, 100);
const interval3 = setInterval(checkElement3, 100);
// 3つの監視で 300ms ごとに処理が走る

Observer Pattern の登場

これらの課題を解決するため、ブラウザベンダーは Observer Pattern に基づいた API を標準化しました。Observer Pattern は「監視対象の変化を購読者に通知する」デザインパターンで、効率的かつ確実なイベント駆動型の監視を実現します。

以下の図は、従来の定期チェック方式と Observer 方式の違いを示しています。

mermaidflowchart TB
  subgraph old["従来方式:定期チェック"]
    timer["setInterval<br/>タイマー"] -->|100ms ごと| check["DOM をチェック"]
    check -->|変化あり?| judge{変化の有無}
    judge -->|Yes| action1["処理実行"]
    judge -->|No| waste["無駄な処理"]
    action1 --> timer
    waste --> timer
  end

  subgraph new["Observer 方式"]
    dom["DOM"] -->|変化発生| observer["Observer API"]
    observer -->|通知| callback["コールバック実行"]
  end

Observer 方式では、変化が発生したときのみコールバックが実行されるため、無駄な処理が大幅に削減されます。

MutationObserver と ResizeObserver の位置づけ

JavaScript には現在、以下の Observer API が用意されています。

#API 名監視内容ブラウザサポート
1MutationObserverDOM ツリーの変更全モダンブラウザ
2ResizeObserver要素のサイズ変化全モダンブラウザ
3IntersectionObserver要素の交差状態全モダンブラウザ
4PerformanceObserverパフォーマンス指標全モダンブラウザ

この記事では、DOM 変化の監視に特化した MutationObserverResizeObserver に焦点を当てます。

課題

DOM 監視における具体的な課題

実際の開発現場では、以下のような課題に直面することが多いでしょう。

課題 1:動的に追加される要素へのイベント登録

SPA やコンポーネントベースのフレームワークでは、DOM が動的に追加・削除されます。

javascript// 課題:後から追加される要素にイベントリスナーを登録したい
document
  .querySelectorAll('.dynamic-button')
  .forEach((button) => {
    button.addEventListener('click', handleClick);
  });

// しかし、このコードの実行後に追加された .dynamic-button には
// イベントリスナーが登録されない

イベント委譲(Event Delegation)で対応できる場合もありますが、すべてのケースに適用できるわけではありません。

課題 2:サードパーティライブラリが DOM を変更した際の検知

Google Maps、チャートライブラリ、広告スクリプトなど、サードパーティのコードが DOM を変更するケースでは、変更タイミングを正確に把握するのが困難です。

javascript// Google Maps API が地図を描画した後に処理を実行したい
const map = new google.maps.Map(element, options);

// しかし、いつ描画が完了するかわからない
// setTimeout での待機は不確実
setTimeout(() => {
  // 地図が完全に描画されているか不明
  applyCustomStyling();
}, 1000);

課題 3:レスポンシブ対応での要素サイズ変化の検知

CSS の @media クエリではウィンドウサイズしか監視できず、特定要素のサイズ変化に応じた処理を実行できません。

javascript// 課題:コンテナのサイズに応じてレイアウトを変更したい
const container = document.querySelector('.container');

// window.resize イベントでは、コンテナ自体のサイズ変化を
// 正確に検知できない
window.addEventListener('resize', () => {
  // ウィンドウサイズは変わったが、
  // container のサイズは変わっていないかもしれない
  const width = container.offsetWidth;
  adjustLayout(width);
});

以下の図は、これらの課題の関係性を示しています。

mermaidflowchart LR
  challenges["DOM 監視の課題"] --> c1["動的要素の<br/>検知"]
  challenges --> c2["サードパーティ<br/>変更の検知"]
  challenges --> c3["要素サイズ<br/>変化の検知"]

  c1 --> need1["MutationObserver<br/>が必要"]
  c2 --> need1
  c3 --> need2["ResizeObserver<br/>が必要"]

  need1 --> solution["適切な Observer<br/>API の選択"]
  need2 --> solution

解決策

MutationObserver による DOM 変化の監視

MutationObserver は DOM ツリーの変更を監視し、変更が発生したときにコールバック関数を実行する API です。

MutationObserver の基本構文

MutationObserver を使用する基本的な流れは以下の通りです。

javascript// 1. コールバック関数を定義
const callback = (mutationsList, observer) => {
  // mutationsList: 発生した変更の配列
  // observer: Observer インスタンス自身
  for (const mutation of mutationsList) {
    console.log('変更タイプ:', mutation.type);
  }
};

次に、Observer インスタンスを作成します。

javascript// 2. MutationObserver インスタンスを作成
const observer = new MutationObserver(callback);

監視対象の要素と、監視するオプションを指定して監視を開始します。

javascript// 3. 監視対象の要素と設定を指定
const targetNode = document.querySelector('#target');
const config = {
  childList: true, // 子ノードの追加・削除を監視
  attributes: true, // 属性の変更を監視
  subtree: true, // 子孫ノード全体を監視
  characterData: true, // テキストノードの変更を監視
};

// 4. 監視を開始
observer.observe(targetNode, config);

監視を停止する場合は disconnect() メソッドを使用します。

javascript// 5. 監視を停止
observer.disconnect();

MutationObserver の設定オプション詳細

設定オプションは用途に応じて適切に選択する必要があります。

javascript// 子ノードの追加・削除のみ監視(最も軽量)
const configChildOnly = {
  childList: true,
};

observer.observe(targetNode, configChildOnly);

属性変更を監視する場合は、古い値の記録や特定属性のフィルタリングも可能です。

javascript// 属性変更を監視し、古い値も記録
const configAttributes = {
  attributes: true,
  attributeOldValue: true, // 古い値を記録
  attributeFilter: ['class', 'data-status'], // 特定属性のみ監視
};

observer.observe(targetNode, configAttributes);

テキストノードの変更を監視するには、characterData オプションを使用します。

javascript// テキストノードの変更を監視
const configText = {
  characterData: true,
  characterDataOldValue: true, // 古い値を記録
  subtree: true, // 子孫のテキストノードも監視
};

observer.observe(targetNode, configText);

MutationRecord オブジェクトの構造

コールバック関数に渡される MutationRecord オブジェクトには、変更の詳細情報が含まれています。

javascriptconst callback = (mutationsList) => {
  for (const mutation of mutationsList) {
    // mutation.type: 変更のタイプ
    // "childList" | "attributes" | "characterData"
    console.log('タイプ:', mutation.type);

    // mutation.target: 変更が発生した要素
    console.log('対象要素:', mutation.target);
  }
};

変更タイプに応じて、異なるプロパティが利用できます。

javascriptconst callback = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      // 追加されたノード
      console.log('追加:', mutation.addedNodes);
      // 削除されたノード
      console.log('削除:', mutation.removedNodes);
    }
  }
};

属性変更の場合は、属性名と古い値を取得できます。

javascriptconst callback = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'attributes') {
      // 変更された属性名
      console.log('属性名:', mutation.attributeName);
      // 古い値(attributeOldValue: true の場合)
      console.log('古い値:', mutation.oldValue);
    }
  }
};

ResizeObserver による要素サイズ変化の監視

ResizeObserver は要素のサイズ変化を監視する API で、CSS の Container Queries のような動作を JavaScript で実現できます。

ResizeObserver の基本構文

ResizeObserver の使用方法は MutationObserver と似ています。

javascript// 1. コールバック関数を定義
const resizeCallback = (entries, observer) => {
  // entries: サイズが変化した要素の配列
  // observer: Observer インスタンス自身
  for (const entry of entries) {
    console.log('サイズ変化:', entry.contentRect);
  }
};

Observer インスタンスを作成し、監視を開始します。

javascript// 2. ResizeObserver インスタンスを作成
const resizeObserver = new ResizeObserver(resizeCallback);

// 3. 監視対象の要素を指定
const targetElement = document.querySelector('.container');
resizeObserver.observe(targetElement);

複数の要素を同時に監視することも可能です。

javascript// 複数要素の監視
const elements = document.querySelectorAll('.resizable');
elements.forEach((element) => {
  resizeObserver.observe(element);
});

監視を停止する場合は unobserve() または disconnect() を使用します。

javascript// 特定要素の監視を停止
resizeObserver.unobserve(targetElement);

// すべての監視を停止
resizeObserver.disconnect();

ResizeObserverEntry オブジェクトの詳細

コールバック関数に渡される ResizeObserverEntry オブジェクトには、複数のサイズ情報が含まれています。

javascriptconst resizeCallback = (entries) => {
  for (const entry of entries) {
    // entry.target: 監視対象の要素
    console.log('要素:', entry.target);

    // entry.contentRect: コンテンツ領域のサイズ(非推奨)
    const { width, height } = entry.contentRect;
    console.log(`サイズ: ${width}x${height}`);
  }
};

より正確なサイズ情報を取得するには、borderBoxSize または contentBoxSize を使用します。

javascriptconst resizeCallback = (entries) => {
  for (const entry of entries) {
    // borderBoxSize: border + padding + content
    const borderBox = entry.borderBoxSize[0];
    console.log('border-box幅:', borderBox.inlineSize);
    console.log('border-box高さ:', borderBox.blockSize);
  }
};

contentBoxSize はパディングを除いたコンテンツ領域のサイズを返します。

javascriptconst resizeCallback = (entries) => {
  for (const entry of entries) {
    // contentBoxSize: content のみ
    const contentBox = entry.contentBoxSize[0];
    console.log('content-box幅:', contentBox.inlineSize);
    console.log('content-box高さ:', contentBox.blockSize);
  }
};

以下の図は、MutationObserver と ResizeObserver の使い分けを示しています。

mermaidflowchart TD
  start["DOM 監視が必要"] --> question{"何を監視?"}

  question -->|DOM 構造・属性・テキスト| mut["MutationObserver<br/>を使用"]
  question -->|要素のサイズ| res["ResizeObserver<br/>を使用"]

  mut --> mut_use["childList, attributes,<br/>characterData<br/>オプションを設定"]
  res --> res_use["borderBoxSize または<br/>contentBoxSize<br/>を参照"]

  mut_use --> impl["監視開始"]
  res_use --> impl

具体例

具体例 1:動的に追加される要素の自動初期化

SPA で新しい要素が追加されたときに、自動的に初期化処理を実行する例です。

まず、監視対象のコンテナを取得し、MutationObserver を設定します。

javascript// アプリケーションのメインコンテナを監視
const appContainer = document.querySelector('#app');

// 追加された要素を初期化するコールバック
const initializeNewElements = (mutationsList) => {
  for (const mutation of mutationsList) {
    // 子ノードの追加のみ処理
    if (mutation.type !== 'childList') continue;

    // 追加されたノードをチェック
    mutation.addedNodes.forEach((node) => {
      // テキストノードは除外
      if (node.nodeType !== Node.ELEMENT_NODE) return;

      // 特定のクラスを持つ要素を初期化
      if (node.classList?.contains('tooltip')) {
        initializeTooltip(node);
      }
    });
  }
};

次に、Observer を作成して監視を開始します。

javascript// Observer を作成
const observer = new MutationObserver(
  initializeNewElements
);

// 監視設定:子ノードの追加と、子孫全体を監視
const config = {
  childList: true,
  subtree: true,
};

// 監視開始
observer.observe(appContainer, config);

ツールチップの初期化関数の実装例です。

javascript// ツールチップ初期化関数
function initializeTooltip(element) {
  const tooltipText = element.getAttribute('data-tooltip');
  if (!tooltipText) return;

  // ツールチップ要素を作成
  const tooltip = document.createElement('div');
  tooltip.className = 'tooltip-content';
  tooltip.textContent = tooltipText;

  // イベントリスナーを登録
  element.addEventListener('mouseenter', () => {
    document.body.appendChild(tooltip);
    positionTooltip(tooltip, element);
  });

  element.addEventListener('mouseleave', () => {
    tooltip.remove();
  });

  console.log('ツールチップを初期化しました:', element);
}

具体例 2:フォーム入力値の変更監視とバリデーション

フォーム要素の属性変更を監視して、リアルタイムバリデーションを実行する例です。

javascript// フォーム要素を取得
const form = document.querySelector('#userForm');

// 属性変更を監視するコールバック
const validateOnChange = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'attributes') {
      const element = mutation.target;
      const attributeName = mutation.attributeName;

      // value 属性の変更を検知
      if (attributeName === 'value') {
        validateField(element);
      }
    }
  }
};

Observer の設定と、バリデーション関数の実装です。

javascript// Observer 設定
const formObserver = new MutationObserver(validateOnChange);
formObserver.observe(form, {
  attributes: true,
  attributeFilter: ['value', 'data-status'],
  subtree: true,
});

// バリデーション関数
function validateField(field) {
  const value = field.value;
  const fieldType = field.getAttribute('data-validate');

  let isValid = true;
  let errorMessage = '';

  switch (fieldType) {
    case 'email':
      isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
      errorMessage =
        'メールアドレスの形式が正しくありません';
      break;
    case 'required':
      isValid = value.trim().length > 0;
      errorMessage = 'この項目は必須です';
      break;
  }

  // エラー表示の切り替え
  updateErrorDisplay(field, isValid, errorMessage);
}

エラー表示の更新処理です。

javascript// エラー表示の更新
function updateErrorDisplay(field, isValid, errorMessage) {
  const errorElement = field.nextElementSibling;

  if (isValid) {
    field.classList.remove('error');
    field.classList.add('valid');
    if (errorElement?.classList.contains('error-message')) {
      errorElement.remove();
    }
  } else {
    field.classList.add('error');
    field.classList.remove('valid');
    if (
      !errorElement?.classList.contains('error-message')
    ) {
      const error = document.createElement('span');
      error.className = 'error-message';
      error.textContent = errorMessage;
      field.after(error);
    }
  }
}

具体例 3:レスポンシブコンテナに応じたレイアウト切り替え

ResizeObserver を使用して、コンテナのサイズに応じてレイアウトを動的に変更する例です。これは CSS Container Queries の JavaScript 実装版です。

javascript// レスポンシブ対応するコンテナを取得
const container = document.querySelector('.card-container');

// サイズ変化に応じたレイアウト調整
const adjustLayout = (entries) => {
  for (const entry of entries) {
    // borderBoxSize を使用(推奨)
    const width = entry.borderBoxSize[0].inlineSize;
    const element = entry.target;

    // 幅に応じてレイアウトクラスを切り替え
    element.classList.remove(
      'layout-small',
      'layout-medium',
      'layout-large'
    );

    if (width < 400) {
      element.classList.add('layout-small');
      console.log('スモールレイアウトに切り替え');
    } else if (width < 800) {
      element.classList.add('layout-medium');
      console.log('ミディアムレイアウトに切り替え');
    } else {
      element.classList.add('layout-large');
      console.log('ラージレイアウトに切り替え');
    }
  }
};

ResizeObserver を作成し、監視を開始します。

javascript// ResizeObserver を作成
const resizeObserver = new ResizeObserver(adjustLayout);

// 監視開始
resizeObserver.observe(container);

初期レイアウトの設定も忘れずに実行します。

javascript// 初期レイアウトの設定
// ResizeObserver は監視開始時に一度コールバックを実行するが、
// 明示的に初期化することも可能
window.addEventListener('DOMContentLoaded', () => {
  const width = container.offsetWidth;
  container.classList.add(
    width < 400
      ? 'layout-small'
      : width < 800
      ? 'layout-medium'
      : 'layout-large'
  );
});

具体例 4:無限スクロールの実装

MutationObserver と IntersectionObserver を組み合わせて、動的に追加されるコンテンツに対して無限スクロールを実装する例です。

javascript// コンテンツコンテナとセンチネル要素
const contentContainer = document.querySelector('#content');
let sentinel = document.querySelector('.sentinel');

// MutationObserver でセンチネル要素の追加を監視
const observeNewSentinel = (mutationsList) => {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach((node) => {
        if (node.classList?.contains('sentinel')) {
          sentinel = node;
          // 新しいセンチネルを IntersectionObserver で監視
          intersectionObserver.observe(sentinel);
          console.log('新しいセンチネルを監視開始');
        }
      });
    }
  }
};

IntersectionObserver でセンチネル要素が表示領域に入ったことを検知します。

javascript// IntersectionObserver でスクロール位置を監視
const intersectionObserver = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        // センチネルが表示されたらコンテンツを読み込む
        loadMoreContent();
      }
    });
  },
  {
    rootMargin: '100px', // 100px 手前で読み込み開始
  }
);

MutationObserver を設定し、コンテンツ読み込み関数を実装します。

javascript// MutationObserver を作成
const mutationObserver = new MutationObserver(
  observeNewSentinel
);
mutationObserver.observe(contentContainer, {
  childList: true,
});

// 初回のセンチネルを監視
if (sentinel) {
  intersectionObserver.observe(sentinel);
}

// コンテンツ読み込み関数
async function loadMoreContent() {
  console.log('コンテンツを読み込み中...');

  // API からデータを取得
  const response = await fetch('/api/content?page=next');
  const data = await response.json();

  // 既存のセンチネルを削除
  sentinel?.remove();

  // 新しいコンテンツを追加
  data.items.forEach((item) => {
    const element = createContentElement(item);
    contentContainer.appendChild(element);
  });

  // 新しいセンチネルを追加(MutationObserver が自動検知)
  const newSentinel = document.createElement('div');
  newSentinel.className = 'sentinel';
  contentContainer.appendChild(newSentinel);
}

具体例 5:テキストエディタの文字数カウンター

contenteditable 要素のテキスト変更をリアルタイムで監視し、文字数を表示する例です。

javascript// エディタ要素と文字数表示要素を取得
const editor = document.querySelector(
  '[contenteditable="true"]'
);
const counter = document.querySelector('.character-count');

// テキスト変更を監視
const updateCharacterCount = (mutationsList) => {
  for (const mutation of mutationsList) {
    // テキスト変更または子ノード変更を検知
    if (
      mutation.type === 'characterData' ||
      mutation.type === 'childList'
    ) {
      const text = editor.textContent;
      const length = text.length;

      // 文字数を更新
      counter.textContent = `${length} 文字`;

      // 制限を超えた場合の警告表示
      const maxLength = 1000;
      if (length > maxLength) {
        counter.classList.add('warning');
        counter.textContent = `${length} / ${maxLength} 文字(超過)`;
      } else {
        counter.classList.remove('warning');
      }
    }
  }
};

MutationObserver の設定と監視開始です。

javascript// Observer を作成
const editorObserver = new MutationObserver(
  updateCharacterCount
);

// テキストノードと子ノードの両方を監視
editorObserver.observe(editor, {
  characterData: true,
  childList: true,
  subtree: true, // 子孫のテキストノードも監視
});

// 初期表示
updateCharacterCount([]);

以下の図は、無限スクロール実装における Observer の連携を示しています。

mermaidsequenceDiagram
  participant User as ユーザー
  participant Scroll as スクロール
  participant IO as IntersectionObserver
  participant Load as loadMoreContent
  participant DOM as DOM
  participant MO as MutationObserver

  User->>Scroll: スクロール
  Scroll->>IO: センチネルが<br/>可視領域に入る
  IO->>Load: コンテンツ読み込み
  Load->>DOM: 新しいコンテンツと<br/>センチネルを追加
  DOM->>MO: DOM 変更を通知
  MO->>IO: 新しいセンチネルを<br/>監視登録
  IO-->>User: 次回スクロール待機

エラー処理とデバッグ

Observer API を使用する際の一般的なエラーと対処法を紹介します。

エラー 1:TypeError: Failed to execute 'observe' on 'MutationObserver'

このエラーは、監視対象が有効な DOM ノードでない場合に発生します。

javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to execute 'observe' on 'MutationObserver':
// parameter 1 is not of type 'Node'.

// 発生条件:null または undefined を observe に渡した場合
const element = document.querySelector('.non-existent');
observer.observe(element, config); // element が null のためエラー

解決方法:要素の存在確認を行う

javascript// 解決策:要素の存在を確認してから監視
const element = document.querySelector('.target');

if (element) {
  observer.observe(element, config);
  console.log('監視を開始しました');
} else {
  console.error('監視対象の要素が見つかりません');
}

エラー 2:TypeError: Failed to construct 'MutationObserver'

コールバック関数が正しく渡されていない場合に発生します。

javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to construct 'MutationObserver':
// 1 argument required, but only 0 present.

// 発生条件:コールバック関数を渡し忘れた場合
const observer = new MutationObserver(); // エラー

解決方法:コールバック関数を必ず渡す

javascript// 解決策:コールバック関数を渡す
const callback = (mutations, observer) => {
  console.log('変更を検知しました');
};

const observer = new MutationObserver(callback);

エラー 3:設定オプションが無効な場合

少なくとも 1 つの監視オプションを true にする必要があります。

javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to execute 'observe' on 'MutationObserver':
// The options object must set at least one of 'attributes',
// 'characterData', or 'childList' to true.

// 発生条件:すべてのオプションが false
const config = {
  childList: false,
  attributes: false,
  characterData: false,
};

observer.observe(element, config); // エラー

解決方法:適切なオプションを設定する

javascript// 解決策:少なくとも1つのオプションを true にする
const config = {
  childList: true, // 子ノードの変更を監視
  attributes: false,
  characterData: false,
};

observer.observe(element, config);

デバッグのベストプラクティス

Observer のデバッグには、以下のようなログ出力が有効です。

javascript// デバッグ用の詳細ログ出力
const callback = (mutationsList, observer) => {
  console.group('MutationObserver コールバック実行');
  console.log('変更数:', mutationsList.length);

  mutationsList.forEach((mutation, index) => {
    console.group(`変更 ${index + 1}`);
    console.log('タイプ:', mutation.type);
    console.log('対象要素:', mutation.target);

    if (mutation.type === 'childList') {
      console.log('追加ノード:', mutation.addedNodes);
      console.log('削除ノード:', mutation.removedNodes);
    } else if (mutation.type === 'attributes') {
      console.log('属性名:', mutation.attributeName);
      console.log('古い値:', mutation.oldValue);
    }

    console.groupEnd();
  });

  console.groupEnd();
};

パフォーマンス計測も重要です。

javascript// パフォーマンス計測
const callback = (mutationsList) => {
  const startTime = performance.now();

  // 処理実行
  mutationsList.forEach((mutation) => {
    // 何らかの処理
    processMutation(mutation);
  });

  const endTime = performance.now();
  const duration = endTime - startTime;

  if (duration > 16) {
    // 16ms を超える場合は警告
    console.warn(
      `処理時間が長すぎます: ${duration.toFixed(2)}ms`
    );
  }
};

まとめ

MutationObserver と ResizeObserver は、現代の Web 開発において DOM の変化を効率的かつ確実に監視するための強力な API です。

重要なポイントのまとめ

MutationObserver の活用場面

  • 動的に追加される DOM 要素の自動初期化
  • サードパーティライブラリによる DOM 変更の検知
  • フォーム入力のリアルタイムバリデーション
  • contenteditable 要素のテキスト変更監視
  • SPA におけるルーティング後の DOM 更新検知

ResizeObserver の活用場面

  • コンテナサイズに応じたレスポンシブレイアウト
  • グラフやチャートの自動リサイズ
  • テキストの overflow 検知と省略表示
  • サイドバーやモーダルのサイズ変化対応
  • CSS Container Queries の代替実装

パフォーマンスに関する注意点

#注意点推奨される対策
1コールバック内の重い処理requestAnimationFrame や debounce で最適化
2subtree: true による広範囲監視必要最小限の範囲に限定する
3Observer の作成し忘れによるメモリリーク不要になったら必ず disconnect() を呼ぶ
4頻繁な DOM 変更による過度なコールバック実行バッチ処理や throttle を導入

ブラウザサポート

MutationObserver と ResizeObserver は、すべてのモダンブラウザでサポートされています。Internet Explorer では MutationObserver のみサポートされており(IE11)、ResizeObserver は polyfill が必要です。

今後の学習ステップ

Observer API をさらに活用するために、以下の学習をおすすめします。

  1. IntersectionObserver - 要素の交差状態を監視し、遅延読み込みや無限スクロールを実装
  2. PerformanceObserver - パフォーマンス指標を監視し、Web Vitals の測定を実装
  3. Resize Observer API の高度な活用 - box-sizing モデルの違いを理解し、適切なサイズ情報を使用
  4. 複数 Observer の連携 - 異なる種類の Observer を組み合わせた高度な UI 実装

DOM 監視の基本を押さえることで、よりインタラクティブで洗練された Web アプリケーションを構築できるようになるでしょう。ぜひ実際のプロジェクトで活用してみてください。

関連リンク