スロットリングを一言でいうと
スロットリング(throttle)は、
「どれだけ連続でイベントが発生しても、“一定間隔ごとにしか処理を実行させない”テクニック です。
スクロール、マウス移動、リサイズなどは、
1秒間に何十回・何百回とイベントが飛びます。
そのたびに重い処理をすると、
画面がカクカクしたり、CPU 使用率が跳ね上がったりします。
スロットリングは、
「最大でも 〇〇ms に 1 回だけ処理する」
という“速度制限”をかけるイメージです。
まずは「スロットリングが欲しくなる状況」をイメージする
スクロールイベントの連発
scroll イベントは、ユーザーが少しスクロールしただけでも大量に発火します。
window.addEventListener("scroll", () => {
console.log("scroll!");
});
JavaScriptこのコードでスクロールすると、
コンソールに「scroll!」がものすごい勢いで流れます。
もしここで、
要素の位置計算
DOM の再レイアウト
API 通信
のような重い処理を毎回やると、
ページが一気に重くなります。
本当にやりたいのは、
「スクロール中ずっと毎回」ではなく、
「ある程度の間隔で様子を見る」ことが多いはずです。
ここでスロットリングです。
「200ms に 1 回だけ処理する」
と決めてしまえば、
イベントは何百回飛んでも、処理は 200ms ごとに 1 回だけになります。
スロットリングの基本的な考え方
「前回実行した時刻から、一定時間経つまで無視する」
スロットリングの動きは、ざっくりこうです。
- 関数が呼ばれたとき、「前回実行した時刻」を見る
- 「前回から指定ミリ秒以上経っていれば」実行する
- そうでなければ、その呼び出しは無視する
つまり、
「前回から 200ms 経っていない呼び出しは全部スキップ」
というルールです。
これにより、
どれだけ連打されても「最大で 200ms に 1 回」しか処理されません。
一番シンプルな throttle 関数を実装してみる
基本形の throttle 実装
まずは、よくあるシンプルな実装から。
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
JavaScriptこの関数は、
fn: 実際に実行したい処理interval: 何ミリ秒ごとに 1 回だけ実行を許すか
を受け取り、
「スロットリングされた新しい関数」を返します。
中でやっていることを分解すると、
前回実行した時刻 lastTime を覚えておく
呼ばれるたびに「今の時刻」との差を計算する
差が interval 以上なら実行し、lastTime を更新
差が interval 未満なら、その呼び出しは無視
という流れです。
ここが重要です。
スロットリングは「呼び出しを間引く」のではなく、
“実際に処理を通す回数を制限する”テクニックです。
イベントは全部来ているけれど、処理は間隔を空けてしか走らない。
実例:スクロールイベントをスロットリングする
スロットリングなしのスクロールハンドラ
まず、スロットリングなしの例。
window.addEventListener("scroll", () => {
console.log("scroll handler");
});
JavaScriptスクロールすると、
コンソールに「scroll handler」が大量に出ます。
ここに重い処理を入れると、
スクロールがカクつきます。
スロットリングありのスクロールハンドラ
さっきの throttle を使ってみます。
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
const onScroll = throttle(() => {
console.log("scroll handler (throttled)");
}, 200);
window.addEventListener("scroll", onScroll);
JavaScriptこれで、
どれだけスクロールしても「200ms に 1 回」しかログが出ません。
実務では、ここに
要素の位置計算
ヘッダーの固定・解除
スクロール位置の保存
などを入れます。
ここが重要です。
スクロールやリサイズのような「高頻度イベント」には、
スロットリングを挟むのが“ほぼ標準装備”だと思っていいです。
デバウンスとの違いをしっかり整理する
デバウンス:最後の 1 回だけ
スロットリング:一定間隔ごとに実行
よく一緒に出てくるのが「デバウンス」です。
違いを感覚で掴んでおきましょう。
デバウンス(debounce)
→ 「最後の 1 回だけ実行したい」
→ 入力が“落ち着いたタイミング”で処理したい
→ 検索ボックス、オートセーブなどに向いている
スロットリング(throttle)
→ 「連続していても、〇〇ms ごとに実行したい」
→ スクロール中も“ある程度追従”してほしい
→ スクロール、リサイズ、マウス移動などに向いている
例えるなら、
デバウンス:
「人の話が完全に終わるまで待ってから返事する」
スロットリング:
「1 秒に 1 回だけ返事していいルール」
ここが重要です。
「落ち着いたタイミングで 1 回だけ」ならデバウンス、
「動いている間もある程度追従したいが、頻度を制限したい」ならスロットリング。
非同期処理とスロットリングの組み合わせ
スクロールに応じて API を叩くとき
例えば「無限スクロール」を考えます。
ユーザーが下までスクロールしたら、次のページを読み込むような UI です。
スクロールイベントのたびに「位置チェック+API 呼び出し」をすると、
やはり負荷が高くなります。
そこで、
「200ms ごとに 1 回だけ位置をチェックする」
というスロットリングを入れます。
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
function checkAndLoadMore() {
const scrollBottom =
window.innerHeight + window.scrollY >= document.body.offsetHeight - 200;
if (scrollBottom) {
loadMore(); // 非同期で次のデータを読み込む
}
}
window.addEventListener("scroll", throttle(checkAndLoadMore, 200));
JavaScriptこうすると、
スクロールイベント自体は大量に発生している
でも checkAndLoadMore は 200ms ごとにしか呼ばれない
その中で「下まで来たか?」をチェックして、必要なら API を叩く
という流れになります。
ここが重要です。
スロットリングは「非同期処理そのもの」を制御するのではなく、
“非同期処理を起動するトリガーの頻度”を制御するテクニックです。
実務でのスロットリングの使いどころ
スクロールに応じたアニメーション・表示制御
要素が画面内に入ったらアニメーションする
スクロール量に応じてヘッダーの透明度を変える
こういった処理は、
スクロールのたびに DOM を触ることになります。
スロットリングを入れることで、
CPU 負荷を抑える
フレーム落ちを減らして、スクロールを滑らかに保つ
といった効果が得られます。
ウィンドウリサイズ時のレイアウト再計算
resize イベントも、
ウィンドウをドラッグしている間は大量に発火します。
そのたびにレイアウト計算や再描画をすると、
ブラウザが重くなります。
「リサイズ中は 200ms ごとに 1 回だけレイアウトを更新する」
というスロットリングは、実務でかなりよく使われます。
マウス移動に応じた処理
マウス追従のエフェクトや、
マウス位置に応じたツールチップ表示なども、
イベント頻度が非常に高いです。
ここでも、
「16〜50ms ごとに 1 回だけ処理する」
といったスロットリングを入れると、
滑らかさと負荷のバランスが取れます。
初心者として「スロットリング」で本当に押さえてほしいこと
スロットリングは、
「高頻度イベントに対して、処理の実行回数を“間引く”テクニック」。
中身はシンプルで、
「前回実行時刻を覚えておき、一定時間経つまで新しい呼び出しを無視する」
という仕組み。
よく使う場面は、
スクロール
リサイズ
マウス移動
デバウンスとの違いは、
「最後の 1 回だけ」ならデバウンス、
「一定間隔ごとに実行したい」ならスロットリング。
そして何より、
「この処理、本当に“イベントが発生するたびに”必要?」
「〇〇ms に 1 回で十分じゃない?」
と自分に問いかけてみてください。
そう感じたら、そこはスロットリングの出番です。
一つ throttle を挟むだけで、
あなたのコードも、ブラウザも、ユーザーの体験も、
ぐっと軽く、滑らかになります。
