logo

AppRouterの@modalで背後のコンテンツのスクロールを制御する

2023-12-14
a year ago

開発環境

  • next 13.5.4

前提

以前の記事で紹介したAppRouterでモーダルを実装するの続きとなります。

この記事ではモーダルの詳細の実装は省いていますのでご了承ください。


本題で説明するモーダルコンポーネントは以下になります。

'use client'
import { useCallback, useRef, useEffect, MouseEventHandler } from 'react'
import { useRouter } from 'next/navigation'

export default function Modal({ children }: { children: React.ReactNode }) {
  const overlay = useRef(null)
  const wrapper = useRef(null)
  const router = useRouter()

  const onDismiss = useCallback(() => {
    router.back()
  }, [router])

  const onClick: MouseEventHandler = useCallback(
    (e) => {
      if (e.target === overlay.current || e.target === wrapper.current) {
        if (onDismiss) onDismiss()
      }
    },
    [onDismiss, overlay, wrapper]
  )

  const onKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Escape') onDismiss()
    },
    [onDismiss]
  )

  useEffect(() => {
    document.addEventListener('keydown', onKeyDown)
    return () => document.removeEventListener('keydown', onKeyDown)
  }, [onKeyDown])

  return (
    <div
      ref={overlay}
      className="fixed z-10 left-0 right-0 top-0 bottom-0 mx-auto bg-black/60"
      onClick={onClick}
    >
      <div
        ref={wrapper}
        className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full sm:w-10/12 md:w-8/12 lg:w-1/2 p-6"
      >
        {children}
      </div>
    </div>
  )
}

本題

公式ドキュメントで紹介されているモーダルをそのまま利用すると、モーダルがマウントした状態で、body要素をスクロールできてしまいます。

これでも問題ないケースはあると思いますが、例えばモーダル自身がフルスクリーンの場合やモーダルの中でスクロール要素がある場合は不自然な動きだとユーザーに認識される可能性があります。

この場合、モーダルコンポーネントに以下の処理を追加するだけで回避できます。

useEffect(() => {
  document.body.style.overflow = 'hidden';

  return () => {
    document.body.style.overflow = 'auto';
  };
}, []);

さいごに

他にも方法があれば教えてください!

参照