Body scroll lockを使用して要素のスクロール固定を制御するカスタムフックを作成する

こんにちは。
イチマルイチデザインのフロントエンドエンジニアの高橋です。

今回は、Body scroll lockを使用したカスタムフックを作成して、ドロワーメニューやモーダルの展開時に、背面の要素を固定する処理を簡単かつシンプルに実装する方法を共有します。

実装方法と解説

こちらが実際のコードの全体像となります。

// useBodyScrollLock.ts

import { RefObject, useEffect } from "react";
import {
 clearAllBodyScrollLocks,
 disableBodyScroll,
 enableBodyScroll,
} from "body-scroll-lock";

type Props = {
 isActive: boolean;
 target: RefObject<HTMLElement>;
}

export function useBodyScrollLock({ isActive, target }: Props) {
 useEffect(() => {
   if (target.current === null) {
     return;
   }

   if (isActive) {
     disableBodyScroll(target.current);
   } else {
     enableBodyScroll(target.current);
   }

   return () => clearAllBodyScrollLocks();
 }, [isActive, target])
}

以下、解説です。

Propsの型定義

type Props = {
 isActive: boolean;
 target: RefObject<HTMLElement>;
}

要素のスクロール固定のオン・オフを制御する isActive と、スクロール固定の実行時に、スクロールを許可する要素 target を定義しています。

呼び出し元のコンポーネントで useRef を使用して、target としてDOMの参照データを受け渡すようにします。

要素のスクロール固定のオン・オフの実装

if (target.current === null) {
 return;
}

if (isActive) {
 disableBodyScroll(target.current);
} else {
 enableBodyScroll(target.current);
}

Body scroll lockの disableBodyScroll メソッドと enableBodyScroll メソッドに、それぞれターゲットとなる要素のDOMの参照データを引数として渡しています。

副作用のクリーンアップの処理

return () => clearAllBodyScrollLocks();

副作用のクリーンアップとして clearAllBodyScrollLocks メソッドを返すように指定します。

この処理を追加することによって、アンマウント時に必ず要素のスクロール固定を解除して、意図しない要素のスクロール固定を回避します。

使用方法

// Example

import React, { useRef, useCallback, useState } from 'react';

export function Component() {
 const [isActive, setIsActive] = useState(false);
 const target = useRef<HTMLDivElement>(null);

 const handleOnDisable = useCallback(() => {
   setIsActive(true);
 }, []);

 const handleOnEnable = useCallback(() => {
   setIsActive(false);
 }, []);

 useBodyScrollLock({
   isActive,
   target
 });

 return (
   <div>
     <button type="button" onClick={handleOnDisable}>Disable Body Scroll</button>
     <button type="button" onClick={handleOnEnable}>Enable Body Scroll</button>
     <div ref={target}>
       Contents...
     </div>
   </div>
 );
}

isActive true の場合は、useBodyScrollLocktarget として渡された要素以外のその他全ての要素に対してスクロールが固定されるようになります。

このようにカスタムフックを作成し使用することで、展開されたドロワーメニューやモーダルの要素に対してのみスクロールを許可する処理が簡単に実装可能となります。

Body scroll lockについて

よくある要素のスクロール固定の実装方法としてbodyに overflow: hidden を付与する方法がありますが、これだけではiOSの端末に対応することができません。

Body scroll lockでは内部の実装として、iOS端末かiOS端末以外で処理を分けており、iOS端末の場合はbodyに対して position: fixed とY軸・X軸のスクロール量に応じた値を topleft の値として渡すような実装になっています。

10KB前後と軽量でシンプルなライブラリですが、スクロールジャンクなどパフォーマンスに考慮した設計となっており、スクロール固定の実装をするのであれば使わない手はないでしょう。

終わりに

ドロワーメニューやモーダルの展開時にスクロール固定の処理を背面要素に加えることで、些細なことですがユーザー体験の向上に繋がるはずです。

よくある実装ですが、ReactベースでのWEBアプリケーションやWEBサイトの開発者にとって少しでも有益な情報になれば幸いです。

株式会社イチマルイチデザインでは、私たちと一緒にReact・Next.js、TypeScriptなどの技術を使ったWEBアプリケーションやWEBサイトの開発をするフロントエンドエンジニアの仲間を募集しています。

デザイナーやバックエンドエンジニアの方も募集をしておりますので、興味がある方はご連絡お待ちしております。

▼採用サイトはこちら
https://101de-sign.com/recruit