【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する
Web アプリケーションを開発していると、動的に変化する DOM を適切に監視したい場面に何度も遭遇しますよね。従来は setInterval やイベントリスナーで無理やり対応していたものの、パフォーマンスの問題や検知漏れに悩まされた経験をお持ちの方も多いのではないでしょうか。
そこで今回は、JavaScript が提供する強力な監視 API である MutationObserver と ResizeObserver の活用方法を徹底解説します。これらの API を使いこなせば、DOM の変化を確実かつ効率的に捉えることができるようになるでしょう。
MutationObserver & ResizeObserver 早見表
Observer 種類別比較表
| # | Observer 種類 | 監視対象 | 主な用途 | パフォーマンス影響 |
|---|---|---|---|---|
| 1 | MutationObserver | DOM の構造変化、属性変化、テキスト変化 | 動的コンテンツの変更検知、DOM 操作の追跡 | 低(非同期実行) |
| 2 | ResizeObserver | 要素のサイズ変化 | レスポンシブ対応、レイアウト調整、コンテナクエリ的実装 | 低(requestAnimationFrame ベース) |
MutationObserver 設定オプション早見表
| # | オプション名 | 型 | 説明 | デフォルト値 |
|---|---|---|---|---|
| 1 | childList | boolean | 子ノードの追加・削除を監視 | false |
| 2 | attributes | boolean | 属性の変更を監視 | false |
| 3 | characterData | boolean | テキストノードの変更を監視 | false |
| 4 | subtree | boolean | 子孫ノード全体を監視 | false |
| 5 | attributeOldValue | boolean | 属性の古い値を記録 | false |
| 6 | characterDataOldValue | boolean | テキストの古い値を記録 | false |
| 7 | attributeFilter | string[] | 監視する属性名を指定 | undefined |
ResizeObserver コールバック情報早見表
| # | プロパティ名 | 型 | 説明 |
|---|---|---|---|
| 1 | contentRect | DOMRectReadOnly | 要素のコンテンツ領域のサイズと位置(非推奨) |
| 2 | borderBoxSize | ResizeObserverSize[] | border-box のサイズ(border + padding + content) |
| 3 | contentBoxSize | ResizeObserverSize[] | content-box のサイズ(content のみ) |
| 4 | devicePixelContentBoxSize | ResizeObserverSize[] | デバイスピクセル単位のサイズ |
| 5 | target | Element | 監視対象の要素 |
背景
DOM 監視の歴史と課題
Web アプリケーションの高度化に伴い、DOM の動的な変更を検知する需要が増大してきました。シングルページアプリケーション(SPA)の普及、サードパーティ製ウィジェットの埋め込み、ユーザー操作に応じた UI の動的変更など、現代の Web 開発では DOM 監視が欠かせません。
従来は以下のような方法で DOM の変化を監視していました。
javascript// 従来の方法:setInterval による定期チェック
let previousHeight = element.offsetHeight;
setInterval(() => {
const currentHeight = element.offsetHeight;
if (currentHeight !== previousHeight) {
console.log('高さが変わりました');
previousHeight = currentHeight;
}
}, 100); // 100ms ごとにチェック
この方法にはいくつかの問題点があります。
javascript// 問題1:不要なチェックによる CPU 負荷
// 変更がなくても 100ms ごとに処理が実行される
// 問題2:検知タイミングの遅延
// 最大 100ms の遅延が発生する可能性がある
// 問題3:複数箇所で監視すると処理が重複
const interval1 = setInterval(checkElement1, 100);
const interval2 = setInterval(checkElement2, 100);
const interval3 = setInterval(checkElement3, 100);
// 3つの監視で 300ms ごとに処理が走る
Observer Pattern の登場
これらの課題を解決するため、ブラウザベンダーは Observer Pattern に基づいた API を標準化しました。Observer Pattern は「監視対象の変化を購読者に通知する」デザインパターンで、効率的かつ確実なイベント駆動型の監視を実現します。
以下の図は、従来の定期チェック方式と Observer 方式の違いを示しています。
mermaidflowchart TB
subgraph old["従来方式:定期チェック"]
timer["setInterval<br/>タイマー"] -->|100ms ごと| check["DOM をチェック"]
check -->|変化あり?| judge{変化の有無}
judge -->|Yes| action1["処理実行"]
judge -->|No| waste["無駄な処理"]
action1 --> timer
waste --> timer
end
subgraph new["Observer 方式"]
dom["DOM"] -->|変化発生| observer["Observer API"]
observer -->|通知| callback["コールバック実行"]
end
Observer 方式では、変化が発生したときのみコールバックが実行されるため、無駄な処理が大幅に削減されます。
MutationObserver と ResizeObserver の位置づけ
JavaScript には現在、以下の Observer API が用意されています。
| # | API 名 | 監視内容 | ブラウザサポート |
|---|---|---|---|
| 1 | MutationObserver | DOM ツリーの変更 | 全モダンブラウザ |
| 2 | ResizeObserver | 要素のサイズ変化 | 全モダンブラウザ |
| 3 | IntersectionObserver | 要素の交差状態 | 全モダンブラウザ |
| 4 | PerformanceObserver | パフォーマンス指標 | 全モダンブラウザ |
この記事では、DOM 変化の監視に特化した MutationObserver と ResizeObserver に焦点を当てます。
課題
DOM 監視における具体的な課題
実際の開発現場では、以下のような課題に直面することが多いでしょう。
課題 1:動的に追加される要素へのイベント登録
SPA やコンポーネントベースのフレームワークでは、DOM が動的に追加・削除されます。
javascript// 課題:後から追加される要素にイベントリスナーを登録したい
document
.querySelectorAll('.dynamic-button')
.forEach((button) => {
button.addEventListener('click', handleClick);
});
// しかし、このコードの実行後に追加された .dynamic-button には
// イベントリスナーが登録されない
イベント委譲(Event Delegation)で対応できる場合もありますが、すべてのケースに適用できるわけではありません。
課題 2:サードパーティライブラリが DOM を変更した際の検知
Google Maps、チャートライブラリ、広告スクリプトなど、サードパーティのコードが DOM を変更するケースでは、変更タイミングを正確に把握するのが困難です。
javascript// Google Maps API が地図を描画した後に処理を実行したい
const map = new google.maps.Map(element, options);
// しかし、いつ描画が完了するかわからない
// setTimeout での待機は不確実
setTimeout(() => {
// 地図が完全に描画されているか不明
applyCustomStyling();
}, 1000);
課題 3:レスポンシブ対応での要素サイズ変化の検知
CSS の @media クエリではウィンドウサイズしか監視できず、特定要素のサイズ変化に応じた処理を実行できません。
javascript// 課題:コンテナのサイズに応じてレイアウトを変更したい
const container = document.querySelector('.container');
// window.resize イベントでは、コンテナ自体のサイズ変化を
// 正確に検知できない
window.addEventListener('resize', () => {
// ウィンドウサイズは変わったが、
// container のサイズは変わっていないかもしれない
const width = container.offsetWidth;
adjustLayout(width);
});
以下の図は、これらの課題の関係性を示しています。
mermaidflowchart LR
challenges["DOM 監視の課題"] --> c1["動的要素の<br/>検知"]
challenges --> c2["サードパーティ<br/>変更の検知"]
challenges --> c3["要素サイズ<br/>変化の検知"]
c1 --> need1["MutationObserver<br/>が必要"]
c2 --> need1
c3 --> need2["ResizeObserver<br/>が必要"]
need1 --> solution["適切な Observer<br/>API の選択"]
need2 --> solution
解決策
MutationObserver による DOM 変化の監視
MutationObserver は DOM ツリーの変更を監視し、変更が発生したときにコールバック関数を実行する API です。
MutationObserver の基本構文
MutationObserver を使用する基本的な流れは以下の通りです。
javascript// 1. コールバック関数を定義
const callback = (mutationsList, observer) => {
// mutationsList: 発生した変更の配列
// observer: Observer インスタンス自身
for (const mutation of mutationsList) {
console.log('変更タイプ:', mutation.type);
}
};
次に、Observer インスタンスを作成します。
javascript// 2. MutationObserver インスタンスを作成
const observer = new MutationObserver(callback);
監視対象の要素と、監視するオプションを指定して監視を開始します。
javascript// 3. 監視対象の要素と設定を指定
const targetNode = document.querySelector('#target');
const config = {
childList: true, // 子ノードの追加・削除を監視
attributes: true, // 属性の変更を監視
subtree: true, // 子孫ノード全体を監視
characterData: true, // テキストノードの変更を監視
};
// 4. 監視を開始
observer.observe(targetNode, config);
監視を停止する場合は disconnect() メソッドを使用します。
javascript// 5. 監視を停止
observer.disconnect();
MutationObserver の設定オプション詳細
設定オプションは用途に応じて適切に選択する必要があります。
javascript// 子ノードの追加・削除のみ監視(最も軽量)
const configChildOnly = {
childList: true,
};
observer.observe(targetNode, configChildOnly);
属性変更を監視する場合は、古い値の記録や特定属性のフィルタリングも可能です。
javascript// 属性変更を監視し、古い値も記録
const configAttributes = {
attributes: true,
attributeOldValue: true, // 古い値を記録
attributeFilter: ['class', 'data-status'], // 特定属性のみ監視
};
observer.observe(targetNode, configAttributes);
テキストノードの変更を監視するには、characterData オプションを使用します。
javascript// テキストノードの変更を監視
const configText = {
characterData: true,
characterDataOldValue: true, // 古い値を記録
subtree: true, // 子孫のテキストノードも監視
};
observer.observe(targetNode, configText);
MutationRecord オブジェクトの構造
コールバック関数に渡される MutationRecord オブジェクトには、変更の詳細情報が含まれています。
javascriptconst callback = (mutationsList) => {
for (const mutation of mutationsList) {
// mutation.type: 変更のタイプ
// "childList" | "attributes" | "characterData"
console.log('タイプ:', mutation.type);
// mutation.target: 変更が発生した要素
console.log('対象要素:', mutation.target);
}
};
変更タイプに応じて、異なるプロパティが利用できます。
javascriptconst callback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
// 追加されたノード
console.log('追加:', mutation.addedNodes);
// 削除されたノード
console.log('削除:', mutation.removedNodes);
}
}
};
属性変更の場合は、属性名と古い値を取得できます。
javascriptconst callback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
// 変更された属性名
console.log('属性名:', mutation.attributeName);
// 古い値(attributeOldValue: true の場合)
console.log('古い値:', mutation.oldValue);
}
}
};
ResizeObserver による要素サイズ変化の監視
ResizeObserver は要素のサイズ変化を監視する API で、CSS の Container Queries のような動作を JavaScript で実現できます。
ResizeObserver の基本構文
ResizeObserver の使用方法は MutationObserver と似ています。
javascript// 1. コールバック関数を定義
const resizeCallback = (entries, observer) => {
// entries: サイズが変化した要素の配列
// observer: Observer インスタンス自身
for (const entry of entries) {
console.log('サイズ変化:', entry.contentRect);
}
};
Observer インスタンスを作成し、監視を開始します。
javascript// 2. ResizeObserver インスタンスを作成
const resizeObserver = new ResizeObserver(resizeCallback);
// 3. 監視対象の要素を指定
const targetElement = document.querySelector('.container');
resizeObserver.observe(targetElement);
複数の要素を同時に監視することも可能です。
javascript// 複数要素の監視
const elements = document.querySelectorAll('.resizable');
elements.forEach((element) => {
resizeObserver.observe(element);
});
監視を停止する場合は unobserve() または disconnect() を使用します。
javascript// 特定要素の監視を停止
resizeObserver.unobserve(targetElement);
// すべての監視を停止
resizeObserver.disconnect();
ResizeObserverEntry オブジェクトの詳細
コールバック関数に渡される ResizeObserverEntry オブジェクトには、複数のサイズ情報が含まれています。
javascriptconst resizeCallback = (entries) => {
for (const entry of entries) {
// entry.target: 監視対象の要素
console.log('要素:', entry.target);
// entry.contentRect: コンテンツ領域のサイズ(非推奨)
const { width, height } = entry.contentRect;
console.log(`サイズ: ${width}x${height}`);
}
};
より正確なサイズ情報を取得するには、borderBoxSize または contentBoxSize を使用します。
javascriptconst resizeCallback = (entries) => {
for (const entry of entries) {
// borderBoxSize: border + padding + content
const borderBox = entry.borderBoxSize[0];
console.log('border-box幅:', borderBox.inlineSize);
console.log('border-box高さ:', borderBox.blockSize);
}
};
contentBoxSize はパディングを除いたコンテンツ領域のサイズを返します。
javascriptconst resizeCallback = (entries) => {
for (const entry of entries) {
// contentBoxSize: content のみ
const contentBox = entry.contentBoxSize[0];
console.log('content-box幅:', contentBox.inlineSize);
console.log('content-box高さ:', contentBox.blockSize);
}
};
以下の図は、MutationObserver と ResizeObserver の使い分けを示しています。
mermaidflowchart TD
start["DOM 監視が必要"] --> question{"何を監視?"}
question -->|DOM 構造・属性・テキスト| mut["MutationObserver<br/>を使用"]
question -->|要素のサイズ| res["ResizeObserver<br/>を使用"]
mut --> mut_use["childList, attributes,<br/>characterData<br/>オプションを設定"]
res --> res_use["borderBoxSize または<br/>contentBoxSize<br/>を参照"]
mut_use --> impl["監視開始"]
res_use --> impl
具体例
具体例 1:動的に追加される要素の自動初期化
SPA で新しい要素が追加されたときに、自動的に初期化処理を実行する例です。
まず、監視対象のコンテナを取得し、MutationObserver を設定します。
javascript// アプリケーションのメインコンテナを監視
const appContainer = document.querySelector('#app');
// 追加された要素を初期化するコールバック
const initializeNewElements = (mutationsList) => {
for (const mutation of mutationsList) {
// 子ノードの追加のみ処理
if (mutation.type !== 'childList') continue;
// 追加されたノードをチェック
mutation.addedNodes.forEach((node) => {
// テキストノードは除外
if (node.nodeType !== Node.ELEMENT_NODE) return;
// 特定のクラスを持つ要素を初期化
if (node.classList?.contains('tooltip')) {
initializeTooltip(node);
}
});
}
};
次に、Observer を作成して監視を開始します。
javascript// Observer を作成
const observer = new MutationObserver(
initializeNewElements
);
// 監視設定:子ノードの追加と、子孫全体を監視
const config = {
childList: true,
subtree: true,
};
// 監視開始
observer.observe(appContainer, config);
ツールチップの初期化関数の実装例です。
javascript// ツールチップ初期化関数
function initializeTooltip(element) {
const tooltipText = element.getAttribute('data-tooltip');
if (!tooltipText) return;
// ツールチップ要素を作成
const tooltip = document.createElement('div');
tooltip.className = 'tooltip-content';
tooltip.textContent = tooltipText;
// イベントリスナーを登録
element.addEventListener('mouseenter', () => {
document.body.appendChild(tooltip);
positionTooltip(tooltip, element);
});
element.addEventListener('mouseleave', () => {
tooltip.remove();
});
console.log('ツールチップを初期化しました:', element);
}
具体例 2:フォーム入力値の変更監視とバリデーション
フォーム要素の属性変更を監視して、リアルタイムバリデーションを実行する例です。
javascript// フォーム要素を取得
const form = document.querySelector('#userForm');
// 属性変更を監視するコールバック
const validateOnChange = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes') {
const element = mutation.target;
const attributeName = mutation.attributeName;
// value 属性の変更を検知
if (attributeName === 'value') {
validateField(element);
}
}
}
};
Observer の設定と、バリデーション関数の実装です。
javascript// Observer 設定
const formObserver = new MutationObserver(validateOnChange);
formObserver.observe(form, {
attributes: true,
attributeFilter: ['value', 'data-status'],
subtree: true,
});
// バリデーション関数
function validateField(field) {
const value = field.value;
const fieldType = field.getAttribute('data-validate');
let isValid = true;
let errorMessage = '';
switch (fieldType) {
case 'email':
isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
errorMessage =
'メールアドレスの形式が正しくありません';
break;
case 'required':
isValid = value.trim().length > 0;
errorMessage = 'この項目は必須です';
break;
}
// エラー表示の切り替え
updateErrorDisplay(field, isValid, errorMessage);
}
エラー表示の更新処理です。
javascript// エラー表示の更新
function updateErrorDisplay(field, isValid, errorMessage) {
const errorElement = field.nextElementSibling;
if (isValid) {
field.classList.remove('error');
field.classList.add('valid');
if (errorElement?.classList.contains('error-message')) {
errorElement.remove();
}
} else {
field.classList.add('error');
field.classList.remove('valid');
if (
!errorElement?.classList.contains('error-message')
) {
const error = document.createElement('span');
error.className = 'error-message';
error.textContent = errorMessage;
field.after(error);
}
}
}
具体例 3:レスポンシブコンテナに応じたレイアウト切り替え
ResizeObserver を使用して、コンテナのサイズに応じてレイアウトを動的に変更する例です。これは CSS Container Queries の JavaScript 実装版です。
javascript// レスポンシブ対応するコンテナを取得
const container = document.querySelector('.card-container');
// サイズ変化に応じたレイアウト調整
const adjustLayout = (entries) => {
for (const entry of entries) {
// borderBoxSize を使用(推奨)
const width = entry.borderBoxSize[0].inlineSize;
const element = entry.target;
// 幅に応じてレイアウトクラスを切り替え
element.classList.remove(
'layout-small',
'layout-medium',
'layout-large'
);
if (width < 400) {
element.classList.add('layout-small');
console.log('スモールレイアウトに切り替え');
} else if (width < 800) {
element.classList.add('layout-medium');
console.log('ミディアムレイアウトに切り替え');
} else {
element.classList.add('layout-large');
console.log('ラージレイアウトに切り替え');
}
}
};
ResizeObserver を作成し、監視を開始します。
javascript// ResizeObserver を作成
const resizeObserver = new ResizeObserver(adjustLayout);
// 監視開始
resizeObserver.observe(container);
初期レイアウトの設定も忘れずに実行します。
javascript// 初期レイアウトの設定
// ResizeObserver は監視開始時に一度コールバックを実行するが、
// 明示的に初期化することも可能
window.addEventListener('DOMContentLoaded', () => {
const width = container.offsetWidth;
container.classList.add(
width < 400
? 'layout-small'
: width < 800
? 'layout-medium'
: 'layout-large'
);
});
具体例 4:無限スクロールの実装
MutationObserver と IntersectionObserver を組み合わせて、動的に追加されるコンテンツに対して無限スクロールを実装する例です。
javascript// コンテンツコンテナとセンチネル要素
const contentContainer = document.querySelector('#content');
let sentinel = document.querySelector('.sentinel');
// MutationObserver でセンチネル要素の追加を監視
const observeNewSentinel = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.classList?.contains('sentinel')) {
sentinel = node;
// 新しいセンチネルを IntersectionObserver で監視
intersectionObserver.observe(sentinel);
console.log('新しいセンチネルを監視開始');
}
});
}
}
};
IntersectionObserver でセンチネル要素が表示領域に入ったことを検知します。
javascript// IntersectionObserver でスクロール位置を監視
const intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// センチネルが表示されたらコンテンツを読み込む
loadMoreContent();
}
});
},
{
rootMargin: '100px', // 100px 手前で読み込み開始
}
);
MutationObserver を設定し、コンテンツ読み込み関数を実装します。
javascript// MutationObserver を作成
const mutationObserver = new MutationObserver(
observeNewSentinel
);
mutationObserver.observe(contentContainer, {
childList: true,
});
// 初回のセンチネルを監視
if (sentinel) {
intersectionObserver.observe(sentinel);
}
// コンテンツ読み込み関数
async function loadMoreContent() {
console.log('コンテンツを読み込み中...');
// API からデータを取得
const response = await fetch('/api/content?page=next');
const data = await response.json();
// 既存のセンチネルを削除
sentinel?.remove();
// 新しいコンテンツを追加
data.items.forEach((item) => {
const element = createContentElement(item);
contentContainer.appendChild(element);
});
// 新しいセンチネルを追加(MutationObserver が自動検知)
const newSentinel = document.createElement('div');
newSentinel.className = 'sentinel';
contentContainer.appendChild(newSentinel);
}
具体例 5:テキストエディタの文字数カウンター
contenteditable 要素のテキスト変更をリアルタイムで監視し、文字数を表示する例です。
javascript// エディタ要素と文字数表示要素を取得
const editor = document.querySelector(
'[contenteditable="true"]'
);
const counter = document.querySelector('.character-count');
// テキスト変更を監視
const updateCharacterCount = (mutationsList) => {
for (const mutation of mutationsList) {
// テキスト変更または子ノード変更を検知
if (
mutation.type === 'characterData' ||
mutation.type === 'childList'
) {
const text = editor.textContent;
const length = text.length;
// 文字数を更新
counter.textContent = `${length} 文字`;
// 制限を超えた場合の警告表示
const maxLength = 1000;
if (length > maxLength) {
counter.classList.add('warning');
counter.textContent = `${length} / ${maxLength} 文字(超過)`;
} else {
counter.classList.remove('warning');
}
}
}
};
MutationObserver の設定と監視開始です。
javascript// Observer を作成
const editorObserver = new MutationObserver(
updateCharacterCount
);
// テキストノードと子ノードの両方を監視
editorObserver.observe(editor, {
characterData: true,
childList: true,
subtree: true, // 子孫のテキストノードも監視
});
// 初期表示
updateCharacterCount([]);
以下の図は、無限スクロール実装における Observer の連携を示しています。
mermaidsequenceDiagram
participant User as ユーザー
participant Scroll as スクロール
participant IO as IntersectionObserver
participant Load as loadMoreContent
participant DOM as DOM
participant MO as MutationObserver
User->>Scroll: スクロール
Scroll->>IO: センチネルが<br/>可視領域に入る
IO->>Load: コンテンツ読み込み
Load->>DOM: 新しいコンテンツと<br/>センチネルを追加
DOM->>MO: DOM 変更を通知
MO->>IO: 新しいセンチネルを<br/>監視登録
IO-->>User: 次回スクロール待機
エラー処理とデバッグ
Observer API を使用する際の一般的なエラーと対処法を紹介します。
エラー 1:TypeError: Failed to execute 'observe' on 'MutationObserver'
このエラーは、監視対象が有効な DOM ノードでない場合に発生します。
javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to execute 'observe' on 'MutationObserver':
// parameter 1 is not of type 'Node'.
// 発生条件:null または undefined を observe に渡した場合
const element = document.querySelector('.non-existent');
observer.observe(element, config); // element が null のためエラー
解決方法:要素の存在確認を行う
javascript// 解決策:要素の存在を確認してから監視
const element = document.querySelector('.target');
if (element) {
observer.observe(element, config);
console.log('監視を開始しました');
} else {
console.error('監視対象の要素が見つかりません');
}
エラー 2:TypeError: Failed to construct 'MutationObserver'
コールバック関数が正しく渡されていない場合に発生します。
javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to construct 'MutationObserver':
// 1 argument required, but only 0 present.
// 発生条件:コールバック関数を渡し忘れた場合
const observer = new MutationObserver(); // エラー
解決方法:コールバック関数を必ず渡す
javascript// 解決策:コールバック関数を渡す
const callback = (mutations, observer) => {
console.log('変更を検知しました');
};
const observer = new MutationObserver(callback);
エラー 3:設定オプションが無効な場合
少なくとも 1 つの監視オプションを true にする必要があります。
javascript// エラーコード:TypeError
// エラーメッセージ:
// TypeError: Failed to execute 'observe' on 'MutationObserver':
// The options object must set at least one of 'attributes',
// 'characterData', or 'childList' to true.
// 発生条件:すべてのオプションが false
const config = {
childList: false,
attributes: false,
characterData: false,
};
observer.observe(element, config); // エラー
解決方法:適切なオプションを設定する
javascript// 解決策:少なくとも1つのオプションを true にする
const config = {
childList: true, // 子ノードの変更を監視
attributes: false,
characterData: false,
};
observer.observe(element, config);
デバッグのベストプラクティス
Observer のデバッグには、以下のようなログ出力が有効です。
javascript// デバッグ用の詳細ログ出力
const callback = (mutationsList, observer) => {
console.group('MutationObserver コールバック実行');
console.log('変更数:', mutationsList.length);
mutationsList.forEach((mutation, index) => {
console.group(`変更 ${index + 1}`);
console.log('タイプ:', mutation.type);
console.log('対象要素:', mutation.target);
if (mutation.type === 'childList') {
console.log('追加ノード:', mutation.addedNodes);
console.log('削除ノード:', mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log('属性名:', mutation.attributeName);
console.log('古い値:', mutation.oldValue);
}
console.groupEnd();
});
console.groupEnd();
};
パフォーマンス計測も重要です。
javascript// パフォーマンス計測
const callback = (mutationsList) => {
const startTime = performance.now();
// 処理実行
mutationsList.forEach((mutation) => {
// 何らかの処理
processMutation(mutation);
});
const endTime = performance.now();
const duration = endTime - startTime;
if (duration > 16) {
// 16ms を超える場合は警告
console.warn(
`処理時間が長すぎます: ${duration.toFixed(2)}ms`
);
}
};
まとめ
MutationObserver と ResizeObserver は、現代の Web 開発において DOM の変化を効率的かつ確実に監視するための強力な API です。
重要なポイントのまとめ
MutationObserver の活用場面
- 動的に追加される DOM 要素の自動初期化
- サードパーティライブラリによる DOM 変更の検知
- フォーム入力のリアルタイムバリデーション
- contenteditable 要素のテキスト変更監視
- SPA におけるルーティング後の DOM 更新検知
ResizeObserver の活用場面
- コンテナサイズに応じたレスポンシブレイアウト
- グラフやチャートの自動リサイズ
- テキストの overflow 検知と省略表示
- サイドバーやモーダルのサイズ変化対応
- CSS Container Queries の代替実装
パフォーマンスに関する注意点
| # | 注意点 | 推奨される対策 |
|---|---|---|
| 1 | コールバック内の重い処理 | requestAnimationFrame や debounce で最適化 |
| 2 | subtree: true による広範囲監視 | 必要最小限の範囲に限定する |
| 3 | Observer の作成し忘れによるメモリリーク | 不要になったら必ず disconnect() を呼ぶ |
| 4 | 頻繁な DOM 変更による過度なコールバック実行 | バッチ処理や throttle を導入 |
ブラウザサポート
MutationObserver と ResizeObserver は、すべてのモダンブラウザでサポートされています。Internet Explorer では MutationObserver のみサポートされており(IE11)、ResizeObserver は polyfill が必要です。
今後の学習ステップ
Observer API をさらに活用するために、以下の学習をおすすめします。
- IntersectionObserver - 要素の交差状態を監視し、遅延読み込みや無限スクロールを実装
- PerformanceObserver - パフォーマンス指標を監視し、Web Vitals の測定を実装
- Resize Observer API の高度な活用 - box-sizing モデルの違いを理解し、適切なサイズ情報を使用
- 複数 Observer の連携 - 異なる種類の Observer を組み合わせた高度な UI 実装
DOM 監視の基本を押さえることで、よりインタラクティブで洗練された Web アプリケーションを構築できるようになるでしょう。ぜひ実際のプロジェクトで活用してみてください。
関連リンク
article【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する
articleJavaScript Drag & Drop API 完全攻略:ファイルアップロード UI を最速で作る
articleJavaScript structuredClone 徹底検証:JSON 方式や cloneDeep との速度・互換比較
articleJavaScript 時刻の落とし穴大全:タイムゾーン/DST/うるう秒の実務対策
articleJavaScript Web Animations API:滑らかに動く UI を設計するための基本と実践
articleJavaScript Service Worker 運用術:オフライン対応・更新・キャッシュ戦略の最適解
articleMistral 使い方入門:要約・説明・翻訳・書き換えの基礎プロンプト 20 連発
articleGitHub Actions のジョブ分割設計:needs と outputs でデータを安全に受け渡す
articleGit rev-spec チートシート:^/~/A..B/A...B を完全図解
article【早見表】JavaScript MutationObserver & ResizeObserver 徹底活用:DOM 変化を正しく監視する
articlehtmx × Laravel/PHP 導入手順:Blade パーシャルとルート設計の落とし穴回避
articleHomebrew の Bottle vs ソースビルド比較検証:時間・サイズ・再現性の差をデータで解説
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来