Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Last active October 24, 2024 05:42
Show Gist options
  • Save manabuyasuda/b98ea85e7638fb7db6875a9322ec228f to your computer and use it in GitHub Desktop.
Save manabuyasuda/b98ea85e7638fb7db6875a9322ec228f to your computer and use it in GitHub Desktop.
要素に横スクロールが発生しているか判定する
import { useState, useEffect, useCallback, RefObject } from 'react';
/**
* ある要素に横スクロールが発生しているかを検知するカスタムフックです。
* コンポーネントのマウント時とリサイズ時にチェックして真偽値を返します。
* @param ref 監視対象の要素への参照
* @returns isScrolled 横スクロールが発生しているか
* @example
* function MyComponent() {
* const containerRef = useRef<HTMLDivElement>(null);
* const isScrolled = useHasHorizontalScroll(containerRef);
*
* return (
* <div ref={containerRef} data-scrolled={isScrolled}>
* </div>
* );
* }
*/
export function useHasHorizontalScroll(ref: RefObject<Element>): boolean {
// 横スクロールの有無を保持する
const [isScrolled, setIsScrolled] = useState(false);
// 横スクロールの有無を判定する
const checkScroll = useCallback(() => {
if (ref.current) {
// 要素のスクロール幅と表示幅を取得する
const { scrollWidth, clientWidth } = ref.current;
// スクロール幅が表示幅より大きい場合は横スクロールが発生していると判定する
setIsScrolled(scrollWidth > clientWidth);
}
}, [ref]);
useEffect(() => {
// リサイズ時の処理を最適化するための変数
let rafId: number | null = null;
// リサイズイベントのハンドラー
const handleResize = () => {
// 前回のrequestAnimationFrameをキャンセルして、リサイズが連続した場合の不要な処理を防ぐ
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
// 新しいrequestAnimationFrameをスケジュールして、ブラウザの描画タイミングで実行する
rafId = requestAnimationFrame(() => {
checkScroll();
// 実行待ちの処理がないことを示し、新しいスケジュールを実行できるようにする
rafId = null;
});
};
// マウント時にチェックする
checkScroll();
// リサイズ時にチェックする
window.addEventListener('resize', handleResize);
// アンマウントやuseEffectの再実行時にリスナーを削除し、保留中のrequestAnimationFrameがあればキャンセルする
return () => {
window.removeEventListener('resize', handleResize);
if (rafId !== null) {
cancelAnimationFrame(rafId);
}
};
}, [checkScroll]);
return isScrolled;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment