MENU

Web API 完全ガイド – IntersectionObserver、MutationObserver など

目次

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 のバランスを取りながら、最適な実装を心がけましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次