MutationObserverでclassや子要素などのDOMの変化を監視する方法

※本ブログの目的は個人の備忘録であり、コードは参考用として掲載しています。
実際に使用される際は、ご自身の環境で十分に動作確認を行ってください。
コードの利用によって生じたいかなる問題についても責任を負いかねますので、あらかじめご了承ください。

JavaScript には、DOMの変化を自動で監視できる便利なAPI MutationObserverがあります。

特に、特定の要素にクラスが付いた/外れた タイミングを検知したいときに非常に便利です。

MutationObserverとは

MutationObserver は、指定したDOMノードの変化を監視し、変化が起きたときにコールバック関数を実行してくれる仕組みです。

「要素が追加・削除された」「クラスが変わった」「属性値が書き換わった」などをリアルタイムで検知できます。

「変化」といっても、監視できる内容はいくつかあります。

  • 属性(class, id, src など)の変更
  • 子要素の追加・削除
  • テキスト内容の変更

このうち今回は、class 属性の変更 について紹介します。

基本の書き方

const target = document.querySelector('.target'); // 監視対象
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log(mutation);
  });
});

// 監視を開始
observer.observe(target, {
  attributes: true,  // 属性の変更を監視
  childList: true,   // 子ノード(追加・削除)の監視
  subtree: false,    // 子孫ノードまで監視するか
});

このように MutationObserver は、

  • コールバック関数を定義
  • observe() で監視を開始する

という流れで使います。

監視オプションについて

observe() メソッドの第2引数には、以下のようなオプションを指定できます。

オプション名

説明

attributes

boolean

要素の属性値(例: class, id, src など)の変更を監視

attributeFilter

string[]

特定の属性のみを監視(例: ["class", "src"]

attributeOldValue

boolean

変更前の属性値を記録してコールバックに渡す

childList

boolean

子要素の追加・削除を監視

subtree

boolean

対象要素の全ての子孫ノードまで監視範囲を拡大

characterData

boolean

テキストノード(文字列)の変更を監視

characterDataOldValue

boolean

テキストの変更前の値を記録してコールバックに渡す

監視の停止

不要になったら、監視を解除できます。

監視対象の要素が削除される場合などには、明示的に解除しておくとメモリリークを防ぐことができます。

observer.disconnect();

要素の変化を監視して処理を実行する

① クラス変更監視

「モーダルが開いたら背景を固定したい」「閉じたら解除したい」など、クラス付与をトリガーにしたUI制御で便利です。

const modal = document.querySelector('.js-modal');

const observer = new MutationObserver(() => {
  if (modal.classList.contains('is-active')) {
    console.log('モーダルが開きました');
  } else {
    console.log('モーダルが閉じました');
  }
});

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

② 動的に追加された要素の検出

リスト要素を動的に追加する場合に便利です。

const container = document.querySelector('.js-list');

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
      if (node.nodeType === 1 && node.classList.contains('item')) {
        console.log('新しいアイテムが追加されました:', node);
      }
    });
  });
});

observer.observe(container, {
  childList: true, // 子ノードの追加・削除を監視
});

node.nodeType === 1とは

これは「追加されたノードが要素ノード(Element Node)であるか」を確認しています。

1は要素ノード(<div><p> など)です。

MutationObserver はテキストノードやコメントノードも検出してしまうため、要素ノードだけを対象にしたい場合にこの条件を使います。

他にも詳しく知りたい方はNode: nodeType プロパティ - Web API | MDNをご確認ください。

③ 別コンポーネント内の状態変化を監視する

他スクリプトやライブラリが data-* 属性を更新したタイミングで自前の処理を走らせたい場合に有効です。

const header = document.querySelector('.js-header');

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    if (mutation.attributeName === 'data-fixed') {
      console.log('ヘッダー固定状態が変更されました:', header.dataset.fixed);
    }
  }
});

observer.observe(header, {
  attributes: true,
  attributeFilter: ['data-fixed'],
});

おわりに

MutationObserver は「クラス変化検知」に限らず、DOMの動的変化をトリガーに処理を走らせたい場面で非常に便利です。

今まで setInterval で無理やり監視していたような場面も、このAPIでスマートに、かつ効率的に実装できますので、ぜひお試しください。