Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Last active February 20, 2025 02:11
Show Gist options
  • Save manabuyasuda/c88db0ec178a49d94f6bb11ac5cbdfbe to your computer and use it in GitHub Desktop.
Save manabuyasuda/c88db0ec178a49d94f6bb11ac5cbdfbe to your computer and use it in GitHub Desktop.
// 任意の引数を受け取る関数型
type AnyFunction = (...args: any[]) => any;
// スロットル制御された関数の型
type ThrottledFunction<T extends AnyFunction> = {
(...args: Parameters<T>): void;
cancel: () => void;
};
/**
* requestAnimationFrameを使用したthrottle関数です。
* cancelメソッドがあるので、クリーンアップ時に処理を完全に停止できます。
* @param {T} callback - スロットルで制御したいコールバック関数
* @returns {ThrottledFunction<T>} スロットル制御された関数とキャンセルメソッドを含むオブジェクト
* @see https://github.com/wuct/raf-throttle
* @example
* import { useEffect } from 'react';
* import rafThrottle from '@/utils/raf-throttle';
*
* const Component = () => {
* useEffect(() => {
* const handleScroll = rafThrottle(() => {
* // スクロール処理
* });
*
* window.addEventListener('scroll', handleScroll);
*
* // クリーンアップ関数
* return () => {
* handleScroll.cancel();
* window.removeEventListener('scroll', handleScroll);
* };
* }, []);
*
* return <div>...</div>;
* };
*/
const rafThrottle = <T extends AnyFunction>(
callback: T,
): ThrottledFunction<T> => {
/**
* アニメーションフレームのリクエストID
* - number:次のフレームでの実行が予約されている状態
* - null:次のフレームでの実行が予約されていない状態
*/
let requestId: number | null = null;
/**
* 最後に呼び出された際の引数を保持する
* - Parameters<T>:次のフレームで実行する際に使用する引数
* - undefined:まだ関数が呼び出されていない、またはキャンセルされた状態
*/
let lastArgs: Parameters<T> | undefined;
/**
* 次のアニメーションフレームで実行される関数を生成する
* @param context - 元の関数のthisコンテキスト
* @returns 次のフレームで実行される関数
*/
const later = (context: any) => () => {
// リクエストIDをリセットする
requestId = null;
// 最後に呼び出された引数が存在する場合、元の関数を実行する
if (lastArgs) {
callback.apply(context, lastArgs);
lastArgs = undefined;
}
};
/** スロットル制御された関数本体 */
const throttled = function (this: any, ...args: Parameters<T>): void {
// 呼び出し時の引数を保持する
lastArgs = args;
// 実行が予約されていない場合、次のフレームでの実行を予約する
if (requestId === null) {
requestId = requestAnimationFrame(later(this));
}
} as ThrottledFunction<T>;
/** 実行のキャンセルして状態もリセットする */
throttled.cancel = () => {
// 予約済みのアニメーションフレームをキャンセルする
if (requestId !== null) {
cancelAnimationFrame(requestId);
// リクエストIDをリセットする
requestId = null;
// 保持していた引数をクリアする
lastArgs = undefined;
}
};
return throttled;
};
export default rafThrottle;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment