JavaScript MutationObserver

概要: このチュートリアルでは、JavaScriptのMutationObserver APIを使用して、DOMツリーに加えられた変更を監視する方法を学びます。

JavaScript MutationObserver APIの紹介

MutationObserver APIを使用すると、DOMツリーに加えられた変更を監視できます。DOMノードが変更されたときに、コールバック関数を呼び出して変更に対応できます。

MutationObserver APIを使用するための基本的な手順は次のとおりです。

まず、DOMが変更されたときに実行されるコールバック関数を定義します。

function callback(mutations) {
    // 
}Code language: JavaScript (javascript)

次に、MutationObserverオブジェクトを作成し、MutationObserver()コンストラクターにコールバックを渡します。

let observer = new MutationObserver(callback);Code language: JavaScript (javascript)

3番目に、observe()メソッドを呼び出して、DOMの変更の監視を開始します。

observer.observe(targetNode, observerOptions);Code language: JavaScript (javascript)

observe()メソッドには2つのパラメーターがあります。targetは、変更を監視するノードのサブツリーのルートです。observerOptionsパラメーターには、監視のコールバックにどのDOM変更を報告する必要があるかを指定するプロパティが含まれています。

最後に、disconnect()メソッドを呼び出して、DOM変更の監視を停止します。

observer.disconnect();Code language: JavaScript (javascript)

MutationObserverのオプション

observe()メソッドの2番目の引数を使用すると、MutationObserverを記述するためのオプションを指定できます。

let options = {
    childList: true,
    attributes: true,
    characterData: false,
    subtree: false,
    attributeFilter: ['attr1', 'attr2'],
    attributeOldValue: false,
    characterDataOldValue: false
};Code language: JavaScript (javascript)

すべてのオプションを使用する必要はありません。ただし、MutationObserverを機能させるには、childListattributes、またはcharacterDataの少なくとも1つをtrueに設定する必要があります。そうしないと、observer()メソッドはエラーをスローします。

子要素の変更の監視

次のリストがあると仮定します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MutationObserver Demo: ChildList</title>
</head>
<body>
    <ul id="language">
        <li>HTML</li>
        <li>CSS</li>
        <li>JavaScript</li>
        <li>TypeScript</li>
    </ul>

    <button id="btnStart">Start Observing</button>
    <button id="btnStop">Stop Observing</button>
    <button id="btnAdd">Add</button>
    <button id="btnRemove">Remove the Last Child</button>

    <script src="app.js"></script>
</body>
</html>
Code language: HTML, XML (xml)

次の例は、子ノードの変更を監視するために、mutation optionsオブジェクトのchildListプロパティを使用する方法を示しています。

まず、querySelector()メソッドを使用して、listbuttonsなどの要素を選択します。デフォルトでは、Stop Observingボタンはdisabledです。

// selecting list
let list = document.querySelector('#language');

// selecting buttons
let btnAdd = document.querySelector('#btnAdd');
let btnRemove = document.querySelector('#btnRemove');
let btnStart = document.querySelector('#btnStart');

let btnStop = document.querySelector('#btnStop');
btnStop.disabled = true;Code language: JavaScript (javascript)

次に、MutationObserverのコールバックとして使用されるlog()関数を宣言します。

function log(mutations) {
    for (let mutation of mutations) {
        if (mutation.type === 'childList') {
            console.log(mutation);
        }
    }
}Code language: JavaScript (javascript)

3番目に、新しいMutationObserverオブジェクトを作成します。

let observer = new MutationObserver(log);
Code language: JavaScript (javascript)

4番目に、Start Observingボタンがクリックされたときに、optionsオブジェクトのchildListtrueに設定してobserve()メソッドを呼び出すことにより、リスト要素の子ノードへのDOM変更の監視を開始します。

btnStart.addEventListener('click', function () {
    observer.observe(list, {
        childList: true
    });
    
    btnStart.disabled = true;
    btnStop.disabled = false;
});
Code language: JavaScript (javascript)

5番目に、addボタンがクリックされたときに、新しいリストアイテムを追加します。

let counter = 1;
btnAdd.addEventListener('click', function () {
    // create a new item element
    let item = document.createElement('li');
    item.textContent = `Item ${counter++}`;

    // append it to the child nodes of list
    list.appendChild(item);
});
Code language: JavaScript (javascript)

6番目に、Removeボタンがクリックされたときに、listの最後の子を削除します。

btnRemove.addEventListener('click', function () {
    list.lastElementChild ?
        list.removeChild(list.lastElementChild) :
        console.log('No more child node to remove');
});
Code language: JavaScript (javascript)

最後に、MutationObserverオブジェクトのdisconnect()メソッドを呼び出すことにより、Stop ObservingボタンがクリックされたときにDOM変更の監視を停止します。

btnStop.addEventListener('click', function () {
    observer.disconnect();    
    // set button states
    btnStart.disabled = false;
    btnStop.disabled = true;
});Code language: JavaScript (javascript)

すべてをまとめる

(function () {
    // selecting the list
    let list = document.querySelector('#language');

    // selecting the buttons
    let btnAdd = document.querySelector('#btnAdd');
    let btnRemove = document.querySelector('#btnRemove');
    let btnStart = document.querySelector('#btnStart');

    // disable the stop button
    let btnStop = document.querySelector('#btnStop');
    btnStop.disabled = true;

    function log(mutations) {
        for (let mutation of mutations) {
            if (mutation.type === 'childList') {
                console.log(mutation);
            }
        }
    }

    let observer = new MutationObserver(log);

    btnStart.addEventListener('click', function () {
        observer.observe(list, {
            childList: true
        });

        btnStart.disabled = true;
        btnStop.disabled = false;
    });

    btnStop.addEventListener('click', function () {
        observer.disconnect();

        // Set the button state
        btnStart.disabled = false;
        btnStop.disabled = true;
    });

    let counter = 1;
    btnAdd.addEventListener('click', function () {
        // create a new item element
        let item = document.createElement('li');
        item.textContent = `Item ${counter++}`;

        // append it to the child nodes of list
        list.appendChild(item);
    });

    btnRemove.addEventListener('click', function () {
        list.lastElementChild ?
            list.removeChild(list.lastElementChild) :
            console.log('No more child node to remove');
    });

})();
Code language: JavaScript (javascript)

すべてのコードがIIFE(即時実行関数式)に配置されていることに注意してください。

属性の変更の監視

属性の変更を監視するには、optionsオブジェクトの次のattributesプロパティを使用します。

let options = {
  attributes: true
}
Code language: JavaScript (javascript)

他の属性を無視しながら、1つ以上の特定のattributesへの変更を監視する場合は、attributeFilterプロパティを使用できます。

let options = {
  attributes: true,
  attributeFilter: ['class', 'style']
}Code language: JavaScript (javascript)

この例では、MutationObserverは、classまたはstyle属性が変更されるたびにコールバックを呼び出します。

サブツリーの変更の監視

ターゲットノードとそのノードのサブツリーを監視するには、optionsオブジェクトのsubtreeプロパティをtrueに設定します。

let options = {
    subtree: true
}
Code language: JavaScript (javascript)

文字データの変更の監視

ノードのテキストコンテンツの変更を監視するには、optionsオブジェクトのcharacterDataプロパティをtrueに設定します。

let options = {
    characterData: true
}Code language: JavaScript (javascript)

古い値へのアクセス

属性の古い値にアクセスするには、optionsオブジェクトのattributeOldValueプロパティをtrueに設定します。

let options = {
    attributes: true,
    attributeOldValue: true
}Code language: JavaScript (javascript)

同様に、optionsオブジェクトのcharacterDataOldValueプロパティをtrueに設定することで、文字データの古い値にアクセスできます。

let options = {
    characterData: true,
    subtree: true,
    characterDataOldValue: true
}Code language: JavaScript (javascript)

MutationObserverの実用的な例

JavaScriptアプリケーションでは、ページ上の要素は通常、動的に生成されます。動的な要素を待つには、MutationObserverを使用する必要があります。

次のwaitForElement()関数は、MutationObserverを使用してセレクターで指定された1つ以上の要素を待ちます。

function waitForElement(selector) {
  return new Promise((resolve) => {
    if (document.querySelector(selector)) {
      return resolve(element);
    }
    const observer = new MutationObserver(() => {
      const element = document.querySelector(selector);
      if (element) {
        resolve(element);
        observer.disconnect();
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  });
}Code language: JavaScript (javascript)

仕組み。

waitForElement()関数は、プロミスを返します。要素が利用可能になると、プロミスが解決されます。

まず、要素が利用可能な場合は要素を解決します。

if (document.querySelector(selector)) {
    return resolve(element);
}Code language: JavaScript (javascript)

次に、要素が利用できない場合は、DOMツリーを監視するための新しいMutationObserverオブジェクトを作成します。

const observer = new MutationObserver(() => {
  const element = document.querySelector(selector);
  if (element) {
    resolve(element);
    observer.disconnect();
  }
});Code language: JavaScript (javascript)

オブザーバーオブジェクトは、要素が利用可能になるとresolve()関数を呼び出し、DOMツリーの監視を停止します。

3番目に、DOMツリー全体の要素を監視します。

observer.observe(document.body, {
      childList: true,
      subtree: true,
});Code language: CSS (css)

waitForElement()Promiseを返すため、次のようにthen()メソッドを使用できます。

waitForElement()('.a-class').then((element) => {
    console.log('Element is ready');
    console.log(element.textContent);
});Code language: JavaScript (javascript)

または、await構文を使用できます。

const element = await waitForElement()('.a-class');
console.log(element.textContent);Code language: JavaScript (javascript)

このチュートリアルでは、DOMの変更を監視し、変更が発生するたびにコールバックを実行するJavaScript MutationObserver APIについて学習しました。

このチュートリアルは役に立ちましたか?