【JavaScript】Debounce / Throttle – 頻繁に発生するイベントを効率的に処理する方法

Webアプリケーションを開発する際、頻繁に発生するイベント(例: スクロールやリサイズ、キーボード入力など)を効率的に処理する必要がある場合があります。その際に役立つテクニックが「Debounce」と「Throttle」です。今回は、それぞれの仕組みと使い方、注意点についてみていきます。

Debounceとは?

Debounce(デバウンス)は、特定のイベントが短時間で何度も発生した場合に、最後の1回だけを実行する仕組みです。これにより、過剰なイベント処理を抑え、パフォーマンスの改善が期待できます。

  • 入力フィールドでのリアルタイム検索。
  • ウィンドウサイズ変更に伴うレイアウト調整。

以下は、テキスト入力フィールドでのリアルタイム検索機能をDebounceで効率化します。

See the Pen debounce by tones (@tonescodedesign) on CodePen.

JavaScript
const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
};

const handleSearch = (event) => {
  const query = event.target.value;
  document.getElementById('result').textContent = `検索結果: ${query}`;
};

const debouncedSearch = debounce(handleSearch, 300);

document.getElementById('search').addEventListener('input', debouncedSearch);
HTML
<input type="text" id="search" placeholder="検索キーワードを入力" />
<p id="result"></p>

このコードでは、ユーザーが入力するたびに検索が即座に行われるのではなく、入力が300ms間隔で安定した場合にのみ実行されます。

Throttleとは?

Throttleは、特定の処理を一定の間隔でしか実行しない方法です。頻繁な呼び出しがあっても、設定した間隔内で1回だけ処理を実行します。

  • スクロールイベントでのパフォーマンス向上。
  • ウィンドウリサイズ時のパフォーマンス改善。

以下は、スクロールイベントにThrottleを適用した例です。

引数

  • func: 実行したい関数
  • limit: 処理を実行する間隔(ミリ秒)

動作

  • 最初に関数が呼び出されたときはすぐに実行
  • その後、指定した limit ミリ秒以内に再び呼び出される場合は、次の実行をスケジュール
  • 最後に実行された時間 (lastRan) と現在の時間 (now) を比較して、次の実行タイミングを調整します

See the Pen throttle by tones (@tonescodedesign) on CodePen.

JavaScript
const progressBar = document.getElementById('progressBar');

// Throttle関数
function throttle(func, limit) {
  let lastRan = 0; // 最後に実行した時刻を保持
  let timeout;     // 次の実行をスケジュールするタイマー

  return function (...args) {
    const context = this;
    const now = Date.now();

    if (now - lastRan >= limit) {
      // 前回の実行から `limit` 経過していれば実行
      func.apply(context, args);
      lastRan = now;
    } else {
      // 経過していない場合は次の実行をスケジュール
      clearTimeout(timeout); // 既存のタイマーをクリア
      timeout = setTimeout(() => {
        func.apply(context, args);
        lastRan = Date.now(); // 実行後にタイムスタンプを更新
      }, limit - (now - lastRan)); // 次回の実行までの残り時間を計算
    }
  };
}

// スクロール処理
function updateProgressBar() {
  const scrollTop = window.scrollY;
  const docHeight = document.body.scrollHeight - window.innerHeight;
  const scrollPercent = (scrollTop / docHeight) * 100;
  progressBar.style.width = `${scrollPercent}%`;
}

// Throttleを適用
window.addEventListener('scroll', throttle(updateProgressBar, 100));
HTML
<div class="progress-bar" id="progressBar"></div>
CSS
body {
  height: 2000px;
  margin: 0;
}
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 8px;
  background: lightblue;
  width: 0%;
}

throttle関数により、スクロールイベントが100msごとに実行されるので、パフォーマンスを維持しながらスムーズな進捗バーが実装できます。

特徴DebounceThrottle
処理の頻度最後のイベント後、一定時間が経過して実行一定間隔ごとに処理を実行
主な用途ユーザー入力の処理、フォームの自動保存などスクロールやリサイズイベントなど

DebounceとThrottleの注意点

適切な用途で選ぶ

  • Debounceを使いすぎると、ユーザーが即時応答を期待している場面で遅延を感じることがあります。
  • Throttleを過剰に使うと、イベントが期待通りに処理されない場合があります。

アクセシビリティの配慮

  • 検索ボックスの入力にDebounceを使用する場合、スクリーンリーダーで適切にフィードバックを提供するようにしましょう。ユーザーが入力結果をリアルタイムで確認できるように、必要に応じて「検索ボタン」を補完的に配置します。
HTML
<label for="search">検索:</label>
<input type="text" id="search" aria-describedby="search-help" />
<button id="searchButton">検索</button>
<div id="search-help">入力を完了後、数秒お待ちください。</div>