Skip to content

DOMの変更を監視するにはMutationObserverを使う

DOM 自体の変更をトリガーにスクリプトを実行したいとき、適切なイベントが無く、実装に苦労したことはないでしょうか。

onchange が使えそうですが、change イベントは input や select といったフォームアイテムにしか使えません。

私も、悩んだ末に setInterval を使って無理やり実装したことがあります。

しかし実は、MutationObserver を使うことで、DOM の変更を監視することができます。

監視できる変更には、次のようなものがあります。

  • ノードの属性の変更
  • ノードのテキストの変更
  • ノードの子孫テキストの変更
  • ノードの子孫ノードの属性の変更
  • ノードの子孫ノードの追加、削除

参考「MutationObserver – Web API インターフェイス | MDN」
https://developer.mozilla.org/ja/docs/Web/API/MutationObserver

ブラウザ実装状況

まずはブラウザの実装状況です。

Can I use (http://caniuse.com/#search=Mutation%20Observer)によると、ほとんどのブラウザで実装されていることが確認できます。

環境のことはほとんど気にせずに利用できます。

基本的な使い方

使い方はとてもシンプルです。

  1. 変更検出時に実行するコールバック関数を引数に渡し、MutationObserver をインスタンス化する。
  2. 監視対象のノードと監視方法を指定するオプションを引数に渡して、作成したインスタンスの observe() メソッドを実行する。

observe() メソッドのオプション

observe() メソッドを実行する際、監視対象などを指定するオプションを渡す必要があります。

オプションには以下の種類があります。

  • childList:対象ノードの子ノードの追加・削除を監視
  • attributes:対象ノードの属性の変更を監視
  • characterData:テキストノードの変更を監視
  • subtree:対象ノードと子孫ノードを監視
  • attributeOldValue:対象ノードの属性の変更前の値を取得できるようにする
  • characterDataOldValue:対象テキストノードの変更前の値を取得できるようにする
  • attributeFilter:指定した属性のみ監視対象とする

これらのオプションのうち、childList, attributes, characterData は、どれか一つが必須となっており、どれも指定しないとエラーになりインスタンス化ができません。

それぞれの挙動を、デモで紹介します。

ノードの子ノードの変更を監視 – childList

childList に true を指定した場合、監視対象ノードの子ノードが変更されたときにコールバック関数が実行されます。

ここでいう子ノードには、子テキストノードも含まれています。

下のデモでは、変更検出時にアラートを表示します。(環境によってはアラートが動作しない場合があります。)

See the Pen MutationObserver childList by okaka (@okaka) on CodePen.light

 

ノードの属性の変更を監視 – attributes

attributes に true を指定した場合、監視対象ノードの属性が変更されたときにコールバック関数が実行されます。

このとき、オプション attributeFilter に配列を渡すことで監視する属性を限定することができます。

下のデモでは attributeFilter に [‘href’, ‘target’] を渡しているので、title 属性の変更は監視対象外となります。

See the Pen MutationObserver attributes by okaka (@okaka) on CodePen.light

 

テキストノードの変更を監視 – characterData

少しわかりにくいのですが、監視対象ノードがテキストノードであり、その値(文字列)を監視したい場合に、characterData に true を指定します。

下のデモでは、

という指定によって、

<p class=”characterData”>characterData</p> の中身(contents)の0個目、つまり、characterData というテキストノードを監視しています。

See the Pen MutationObserver characterData by okaka (@okaka) on CodePen.light

 

ノードの子孫ノードの変更を監視 – subtree

必須オプションに加えて subtree に true を指定した場合、監視対象ノードの子孫ノードすべてが監視対象となります。

下のデモでは、

childList: true, attributes: true, subtree: true と指定しているため、

  • 対象ノードの属性
  • 子ノードの属性
  • 子孫ノードの属性
  • 子ノードの追加削除
  • 子孫ノードの追加削除

が監視対象となっています。

See the Pen MutationObserver subtree by okaka (@okaka) on CodePen.light

 

コールバックの引数(MutationRecords)

変更を感知したときに発火するコールバックでは、起こった変更についての情報を参照することができます。

この情報は MutationRecord というオブジェクトに格納されており、コールバックの引数に配列形式で渡されます。

同時に複数の変更があればそのすべての記録が含まれた配列が渡されることになります。

MutationRecord のもつプロパティのうち、特に重要なものは次の7つです。

  • type: “childList”の変更なのか “attributes” の変更なのか “characterData” の変更なのか
  • addedNodes:追加されたノードの配列
  • removedNodes:削除されたノードの配列
  • previousSibling:追加削除されたノードの直前のノード
  • nextSibling:追加削除されたノードの直後のノード
  • attributeName:変更された属性名
  • oldValue:変更された属性 or 変更されたテキストの変更前の値

なお、oldValue を取得するためには、observe() メソッドのオプションの attributeOldValue か characterDataOldValue に true を指定しておく必要があります。

下のデモでは、この7つの値をコンソールに出力します。

コンソールを開いてボタンを押してみてください。

See the Pen MutationObserver MutationRecords by okaka (@okaka) on CodePen.light

 

メソッド

MutationObserver には、observe() メソッド以外に、2つメソッドが用意されています。

  • disconnect():監視を解除する
  • takeRecords():変更を検知してから、まだコールバック関数が実行されていない MutationRecord の配列を削除する。削除された配列は返り値で取得できる

disconnect() メソッドは特に難しくないですが、takeRecords() は少しクセがあります。

MutationObserver は、変更を検知したあと、一通り実行中のスクリプトが終了してからコールバック関数を実行するのですが、takeRecords() はこの挙動に手を加えたいときに利用します。

See the Pen MutationObserver Methods by okaka (@okaka) on CodePen.light

 

このデモでは、addChildTrees ボタンには次のような処理が実装されています。

  1. li 要素を追加
  2. コンソールへ出力
  3. li 要素を追加
  4. コンソールへ出力

これを実際に動かしてみると、インスタンス作成時に定義したコールバック関数が実行されるのは2つめのコンソールへの出力の後となっており、2つの MutationRecord がコールバック関数にまとめて渡されていることが確認できます。

一方、takeRecords ボタンには次のような処理が実装されています。

  1. li 要素を追加
  2. コンソールへ出力
  3. takeRecords() メソッドを実行
  4. li 要素を追加
  5. コンソールへ出力
  6. takeRecords() メソッドを実行

こちらでは、要素が変更されて MutationRecord が作成されるたびにtakeRecords() メソッドを実行するようにしています。

これによって、インスタンス作成時に定義したコールバック関数が実行されるタイミングより早く、MutationRecord を取得することができています。

また takeRecords() メソッドは、実行されるたびにコールバック関数に渡されるはずだった MutationRecord の配列を空にするので、コールバック関数自体は実行されていません。

このように takeRecords() メソッドは、次の変更をする前になんらかの処理を実行する必要がある場合や、特定の条件ではコールバック関数を実行したくない場合などに使えるメソッドになっています。

おわりに

DOM の変更を監視できるので、場合によっては非常に強力な手段になります。

ただ、全ての変更を拾ってしまうため、多様する場合は他のイベントとの競合、誤爆に注意しなければなりません。

DOM をゴリゴリ操作するのは最近の流行とも違いますし、どうしても必要な場合にだけ使っていくのがよいでしょう。

Comments are closed.