【JavaScript】Mutation ObserverでDOMを監視する

# Mutation Observerとは

DOMの変化を監視することができるWeb APIである。

# Mutation Observerの使い方

## 監視対象の要素を設定

まず、監視対象の要素を決めなければならない。
以下は idがsampleの要素を取得し、監視対象に設定している。

```js
// idがsampleの要素を監視対象に設定
const targetNode = document.getElementById('sample');
```

全体DOMを監視対象とするには、以下のようにする。

```js
// DOM全体を監視対象に設定
const targetNode = document.documentElement;
```

##  監視オプションの設定

以下のコードのようにオプションを設定する。以下の例では監視対象ノードとその子孫ノードの属性の変更や子要素の追加/削除、さらにはテキストノードの変更までを監視する。

```js
// 監視オプションの設定
const config = { attributes: true, childList: true, subtree: true };
```

### オプションの一覧

attributeFilter以外は全部bool値である。

| オプション名              | 説明                                             |
|-------------------------|--------------------------------------------------|
| childList               | 子要素の追加や削除の変更を監視するかどうか。       |
| attributes              | 属性の変更を監視するかどうか。                |
| characterData           | テキストノードの内容の変更を監視するかどうか。      |
| subtree                 | 監視対象ノードの子孫ノード全体の変更を監視するかどうか。|
| attributeOldValue       | attributesがtrueの場合、変更前の属性値を保持する。  |
| characterDataOldValue   | characterDataがtrueの場合、変更前のテキストノードの内容を保持する。|
| attributeFilter         | 監視する特定の属性名の配列。この配列に含まれない属性の変更は無視される。  |

## コールバック関数の設定

監視対象に変更が生じた際に実行されるコールバック関数を設定する。

```js
// Mutation Observerのコールバック関数
const handleMutation = (mutationsList, observer) => {
  mutationsList.forEach(mutation => {
    // 変更に対する処理
    console.log('DOMが変更されました:', mutation);
  });
};
```

コールバック関数の第1引数(上記の場合はmutationsList)には[MutationRecord](https://developer.mozilla.org/ja/docs/Web/API/MutationRecord)オブジェクトの配列が入っている。MutationRecordにはDOM変化の情報が入ってる。

## 監視を開始する

observerのインスタンスを作成し、**observe()** で監視を開始する。

```js
// コールバック関数に結びつけられたオブザーバーのインスタンスを生成
const observer = new MutationObserver(handleMutation);
// 対象ノードの設定された変更の監視を開始
observer.observe(targetNode, options);
```

## 監視の終了

**disconnect()** で終了できる

```js
observer.disconnect();
```
## 監視できないもの

監視対象のイベントハンドラーは監視することができない。

# 実際に使ってみた例

## jQuery

jQueryで`$(document).ready(function() {});`や`$(window).on('load', function(){});`を使った場合だと、タイミングが合わないときがある。

たとえば、以下のように実装すると、`$(window).on('load', function(){});`がsampleの要素が存在しないというエラーがでるときがある。

```js
$(window).on('load', function(){
    $('#sample').after('<p>hoge</p>')
});
```

ちなみに以下のように時間を空けると成功する。

```js
$(window).on('load', function(){
 setTimeout(function() {
    $('#sample').after('<p>hoge</p>')
  }, 1000); 
});
```

しかし、この場合だと最適な時間を設定するのが難しい。
そこで **Mutation Observer** を用いる。

```js
// 監視する範囲を指定（document.documentElementは全体のDOMを対象とする）
const targetNode = document.documentElement; 
// オプションを設定
const config = { childList: true, subtree: true };

// Mutation Observerの作成
const observer = new MutationObserver(function (mutationsList, observer) {
  for (let mutation of mutationsList) {
    if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
      // 追加されたノードをチェック
      for (let node of mutation.addedNodes) {
        if ($(node).is("#sample")){
          // 監視を終了する
          observer.disconnect();
          // 追加されたら必要な処理を実行
          $('#sample').after('<p>hoge</p>')
        }
      }
    }
  }
});

// Mutation Observerを開始
observer.observe(targetNode, config);
```

## React

Mutaion Observerを使用して、カスタムフックを作成する例があったので、参考に挙げておく。
[【React hooks】要素の変化を検知して何かをするカスタムフックを作成する](https://tsukulog.net/2021/01/14/mutation-observer-custom-hooks/)

# 参考文献

1. [MutationObserver の使い方](https://www.webdesignleaves.com/pr/jquery/mutationObserver.html)
1. [Mutation observer](https://ja.javascript.info/mutation-observer)