Web API 完全ガイド – IntersectionObserver、MutationObserver など
ネイティブ Web API の活用
Web API はブラウザが提供するネイティブ機能です。jQuery や lodash のような外部ライブラリに頼らず、標準 API だけで高度な機能を実現できます。IntersectionObserver、MutationObserver、RequestAnimationFrame など、フロントエンド開発で活躍する主要 API を紹介します。
対象となる方
- フロントエンドエンジニア、特に React・Vue・Svelte などフレームワーク開発者
- パフォーマンス最適化を検討している方
- ネイティブ API を活用してバンドルサイズを削減したい方
※ このドキュメントは JavaScript ES6 以上を対象とします。
IntersectionObserver – 要素の表示検出
IntersectionObserver の基本
IntersectionObserver は、要素がビューポート(画面領域)に表示されたかを効率的に検出します。スクロールイベントと異なり、ブラウザが最適なタイミングで実行されるため、パフォーマンスに優れています。
// 基本的な使用方法
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('要素が表示されました:', entry.target);
// 要素が表示されたときの処理
} else {
console.log('要素が非表示になりました:', entry.target);
}
});
});
// 監視対象の要素を指定
const element = document.getElementById('lazy-image');
observer.observe(element);
// 監視を停止
observer.unobserve(element);
IntersectionObserver のオプション
// オプション付きの例
const options = {
root: null, // 監視する親要素(null = ビューポート)
rootMargin: '0px', // ビューポートの周辺マージン
threshold: 0.5, // 50%表示されたときに実行(0~1の範囲)
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, options);
実装例:遅延ロード(Lazy Loading)
// 画像の遅延ロード実装
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // data-src から実際の画像URLを取得
img.classList.add('loaded');
observer.unobserve(img); // ロード後は監視を解除
}
});
}, {
rootMargin: '50px', // 画面外50pxの時点でロード開始
});
// すべての遅延ロード画像を監視
document.querySelectorAll('img[data-src]').forEach((img) => {
imageObserver.observe(img);
});
MutationObserver – DOM 変更の監視
MutationObserver の基本
MutationObserver は DOM の変更(ノード追加・削除・属性変更など)をリアルタイムで検出します。動的にコンテンツが追加されるアプリケーションで非常に有用です。
// 基本的な使用方法
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
console.log('子要素が追加・削除されました');
console.log('追加:', mutation.addedNodes);
console.log('削除:', mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log('属性が変更されました:', mutation.attributeName);
} else if (mutation.type === 'characterData') {
console.log('テキストが変更されました');
}
});
});
// 監視オプション
const options = {
childList: true, // 子要素の追加・削除を監視
attributes: true, // 属性の変更を監視
characterData: true, // テキストコンテンツの変更を監視
subtree: true, // 子孫要素のすべての変更を監視
};
const container = document.getElementById('content');
observer.observe(container, options);
// 監視を停止
observer.disconnect();
実装例:リアルタイム検出
// チャットアプリの例:新しいメッセージを自動スクロール
const chatObserver = new MutationObserver(() => {
const chatContainer = document.getElementById('messages');
// 最新メッセージまでスクロール
chatContainer.scrollTop = chatContainer.scrollHeight;
});
chatObserver.observe(document.getElementById('messages'), {
childList: true, // 新しいメッセージ(li要素)の追加を監視
subtree: true,
});
ResizeObserver – リサイズ検出
ResizeObserver の基本
ResizeObserver は要素のサイズ変更を検出します。メディアクエリの代替として、JavaScript での動的レイアウト調整に活用されます。
// 基本的な使用方法
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const { width, height } = entry.contentRect;
console.log(`新しいサイズ: ${width}px × ${height}px`);
// サイズに応じてレイアウトを調整
if (width < 600) {
entry.target.classList.add('mobile');
} else {
entry.target.classList.remove('mobile');
}
});
});
const element = document.getElementById('responsive-box');
resizeObserver.observe(element);
RequestAnimationFrame - スムーズなアニメーション
RequestAnimationFrame の重要性
requestAnimationFrame はブラウザの更新頻度(通常 60FPS)に同期してアニメーションを実行します。setTimeout と異なり、60FPS のスムーズなアニメーションが実現できます。
// スムーズなアニメーション例
let animationId;
const box = document.getElementById('animated-box');
let position = 0;
function animate() {
position += 5;
box.style.transform = `translateX(${position}px)`;
if (position < 300) {
animationId = requestAnimationFrame(animate);
}
}
// アニメーション開始
animate();
// アニメーション停止
cancelAnimationFrame(animationId);
スクロールアニメーション
// スムーズなスクロールアニメーション
function smoothScroll(target, duration = 1000) {
const start = window.scrollY;
const distance = target - start;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// easing function(イージング)
const easeInOutQuad = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
window.scrollTo(0, start + distance * easeInOutQuad);
if (progress < 1) {
requestAnimationFrame(animation);
}
}
requestAnimationFrame(animation);
}
// 使用例
smoothScroll(document.getElementById('target').offsetTop);
Fetch API と AbortController
Fetch API の基本
Fetch API は XMLHttpRequest の代替として、Promise ベースのシンプルなインターフェースを提供します。
// 基本的な GET リクエスト
fetch('/api/users')
.then((response) => {
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
})
.then((data) => console.log('データ取得成功:', data))
.catch((error) => console.error('エラー:', error));
// async/await を使った実装
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('ネットワークエラー');
const data = await response.json();
return data;
} catch (error) {
console.error('データ取得失敗:', error);
}
}
AbortController でリクエストをキャンセル
// AbortController を使ったキャンセル
const controller = new AbortController();
// リクエストを送信
fetch('/api/slow-endpoint', { signal: controller.signal })
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => {
if (error.name === 'AbortError') {
console.log('リクエストがキャンセルされました');
} else {
console.error('エラー:', error);
}
});
// 3秒後にリクエストをキャンセル
setTimeout(() => controller.abort(), 3000);
検索入力のキャンセル例
// 検索ワードが変更されるたびに前のリクエストをキャンセル
let searchController = null;
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', async (e) => {
const query = e.target.value;
// 前のリクエストをキャンセル
if (searchController) searchController.abort();
// 新しいリクエストを作成
searchController = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: searchController.signal,
});
const results = await response.json();
console.log('検索結果:', results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('検索エラー:', error);
}
}
});
実装例:無限スクロール
IntersectionObserver を使った無限スクロール
// 無限スクロール実装
let page = 1;
let isLoading = false;
const observer = new IntersectionObserver(
async (entries) => {
if (entries[0].isIntersecting && !isLoading) {
isLoading = true;
try {
const response = await fetch(`/api/posts?page=${page}`);
const posts = await response.json();
// DOMに新しいポストを追加
const container = document.getElementById('posts');
posts.forEach((post) => {
const element = document.createElement('div');
element.className = 'post';
element.innerHTML = `${post.title}
${post.content}
`;
container.appendChild(element);
});
page++;
} catch (error) {
console.error('ポスト読み込みエラー:', error);
} finally {
isLoading = false;
}
}
},
{ rootMargin: '100px' }
);
// 最後の要素を監視
const lastElement = document.getElementById('sentinel');
observer.observe(lastElement);
Web API 比較表
| API | 用途 | 主な特徴 | ブラウザ対応 |
| IntersectionObserver | 要素の表示検出 | パフォーマンス優秀、遅延ロード | IE 非対応 |
| MutationObserver | DOM 変更検出 | リアルタイム監視、柔軟な設定 | IE 非対応 |
| ResizeObserver | リサイズ検出 | メディアクエリの代替 | IE 非対応 |
| requestAnimationFrame | アニメーション | 60FPS、ブラウザ最適化 | すべてのモダンブラウザ |
| Fetch API | ネットワーク通信 | Promise ベース、AbortController | IE 非対応 |
ベストプラクティス
パフォーマンス最適化のコツ
- IntersectionObserver を使い、スクロールイベントリスナーを避ける
- MutationObserver は慎重に使用(パフォーマンスに影響)
- requestAnimationFrame は毎フレーム実行され続けるため、不要になったら必ず cancel する
- 複数の API を監視する場合、不要になったら
disconnect()を呼び出す
ポリフィル対応
// 古いブラウザ対応チェック
if (!window.IntersectionObserver) {
console.warn('このブラウザは IntersectionObserver をサポートしていません');
// フォールバック処理
}
// Fetch ポリフィル(古いブラウザ用)
if (!window.fetch) {
// fetch ポリフィルライブラリを読み込む
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/whatwg-fetch@3';
document.head.appendChild(script);
}
まとめ
Web API はサードパーティライブラリの代替になることが多いです。ネイティブ API を活用することで以下が実現できます:
- バンドルサイズの削減
- パフォーマンスの向上
- ブラウザの最適化の恩恵を受ける
- 外部ライブラリのメンテナンス負担軽減
IntersectionObserver、MutationObserver、RequestAnimationFrame などのネイティブ API を理解し、活用してください。モダンブラウザではほぼすべてサポートされており、古いブラウザはポリフィルで対応できます。パフォーマンスと UX のバランスを取りながら、最適な実装を心がけましょう。
