【JavaScript】MutationObserverによるDOM変更の監視

【JavaScript】MutationObserverによるDOM変更の監視

MutationObserverは、DOMツリー(HTMLの構造)への変更を非同期で監視するためのAPIとして使われます。要素の追加・削除、属性の変化、テキストの変更など、理解すれば割と便利な機能です。

JavaScript #Javascript#MutationObserver

【JavaScript】MutationObserverによるDOM変更の監視

サムネイル

MutationObserverは、DOMツリー(HTMLの構造)への変更を非同期で監視するためのAPIとして使われます。要素の追加・削除、属性の変化、テキストの変更など、理解すれば割と便利な機能です。

更新日: 7/10/2025

使用方法

MutationObserverの利用は、主に2つのステップで構成されます。

  1. 変更を検知した際に実行されるコールバック関数を引数に、MutationObserverのインスタンスを作成します。
  2. 作成したインスタンスのobserveメソッドを使い、監視対象のDOMノードと監視内容(オプション)を指定して監視を開始します。
// 1. 監視対象のノードを選択
const targetNode = document.getElementById('target');

// 2. 変更を検出したときに実行されるコールバック関数を定義
const callback = (mutationList, observer) => {
  for (const mutation of mutationList) {
    if (mutation.type === 'childList') {
      console.log('子ノードが追加または削除されました。');
    } else if (mutation.type === 'attributes') {
      console.log(`属性「${mutation.attributeName}」が変更されました。`);
    } else if (mutation.type === 'characterData') {
      console.log('テキストデータが変更されました。');
    }
  }
};

// 3. オブザーバーのインスタンスを作成
const observer = new MutationObserver(callback);

// 4. 監視を開始
observer.observe(targetNode, {
  childList: true,      // 子ノードの追加・削除
  attributes: true,     // 属性の変更
  characterData: true,  // テキストデータの変更
  subtree: true         // 子孫ノードも監視
});

設定オプション

observeメソッドの第二引数では、何を監視するかを細かく設定できます。

オプション 説明
childList trueにすると、ターゲットの子ノードの追加・削除を監視します。
attributes trueにすると、ターゲットの属性の変更を監視します。
characterData trueにすると、ターゲットのテキストコンテンツの変更を監視します。
subtree trueにすると、ターゲットだけでなく、その子孫ノード全ての変更を監視します。
attributeFilter 監視対象とする属性名を配列で指定します。(例: ['class', 'style']
attributeOldValue trueにすると、変更前の属性値をmutation.oldValueで取得できます。
characterDataOldValue trueにすると、変更前のテキストデータをmutation.oldValueで取得できます。

動的コンテンツの監視

チャットのメッセージリストのように、要素が動的に追加されるコンテナを監視するケースです。新しいメッセージが追加されたことを検知し、自動スクロールなどの処理を実行できます。

const chatContainer = document.getElementById('chat-messages');

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach((node) => {
        // 新しい要素が追加された場合
        if (node.nodeType === Node.ELEMENT_NODE) {
          // 最下部へ自動スクロール
          chatContainer.scrollTop = chatContainer.scrollHeight;
        }
      });
    }
  });
});

observer.observe(chatContainer, {
  childList: true,
  subtree: true // メッセージ内の要素変更も検知する場合
});

属性の変更監視

body要素のclass属性を監視し、ダークテーマとライトテーマの切り替えを検知する例です。これにより、テーマの変更に応じて追加処理を実行できます。

const bodyElement = document.body;

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
      const currentTheme = bodyElement.classList.contains('dark-theme') ? 'dark' : 'light';
      // 現在のテーマをローカルストレージに保存するなどの処理
      localStorage.setItem('theme', currentTheme);
    }
  });
});

observer.observe(bodyElement, {
  attributes: true,
  attributeFilter: ['class'] // class属性のみを監視
});

注意点

MutationObserverは高パフォーマンスですが、使い方を誤るとちょっと困ったことが発生しがちです。

ISSUE - 課題

無限ループの回避

コールバック関数内で監視対象のDOMをさらに変更すると、その変更が新たなMutationRecordを生成し、コールバックが再度呼び出され、無限ループに陥る危険があります。

例えば、要素が追加されたら、その要素に新しい子要素を追加する、といった処理です。これを防ぐには、処理を実行する前に特定の属性(例: data-processed)をチェック・付与するなど、処理が一度しか実行されないようにする工夫が必要です。

メモリリークの防止

addEventListenerと同様に、MutationObserverも不要になったら必ず監視を停止する必要があります。特に、コンポーネントベースのフレームワーク(React, Vueなど)では、コンポーネントが破棄されるライフサイクルでdisconnectメソッドを呼び出すことが重要です。

class MyComponent {
  constructor() {
    this.observer = new MutationObserver(/* ... */);
    // ...監視を開始...
  }

  // コンポーネントが不要になるときに呼び出す
  destroy() {
    if (this.observer) {
      this.observer.disconnect(); // 監視を停止
      this.observer = null;
    }
  }
}

監視範囲の最適化

パフォーマンスを維持するため、監視は必要最小限の範囲とオプションに留めるべきです。例えば、document.body全体をsubtree: trueで監視するような広範囲な設定は、意図しない多くの変更を検知してしまい、パフォーマンスに影響を与える可能性があるため推奨されません。

まとめ

MutationObserverは、DOMの変更を監視するために役立ちますが、無限ループやメモリリークといった問題を防ぐため、disconnectによる適切な後処理と、監視範囲の最適化を常に意識することが重要です。

検索

検索条件に一致する記事が見つかりませんでした