使用方法
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による適切な後処理と、監視範囲の最適化を常に意識することが重要です。
コメント