使用方法
MutationObserver
の利用は、主に2つのステップで構成されます。
- 変更を検知した際に実行されるコールバック関数を引数に、
MutationObserver
のインスタンスを作成します。 - 作成したインスタンスの
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
は高パフォーマンスですが、使い方を誤るとちょっと困ったことが発生しがちです。
無限ループの回避
コールバック関数内で監視対象の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
による適切な後処理と、監視範囲の最適化を常に意識することが重要です。