Skip to content

useRemoveTopPageScroll

이토 edited this page Jun 1, 2025 · 2 revisions

문제 상황

모바일 환경에서 모달을 띄우거나 특정 레이어를 오버레이할 때, 배경 콘텐츠가 여전히 스크롤 가능해 UI가 흔들리거나 클릭이 오작동하는 문제가 있었습니다.

특히 모바일 디바이스에서 페이지 최상단 요소에 대해 overflow-hidden 클래스를 조건부로 적용해도, 디바이스 리사이즈 상황(회전 포함)에서는 스크롤이 여전히 남는 버그가 발생했습니다.

이는 단순한 스타일 이슈가 아닌, UX의 일관성과 안정성에 영향을 주는 문제로 판단했습니다.

어떻게 해결했을까?

이 문제를 해결하기 위해 다음과 같은 구조적 접근을 설계했습니다:

  1. 디바이스 타입 감지 훅 추출 (useBreakpoint)

뷰포트의 너비를 감지해 mobile, tablet 등 디바이스 타입을 반환하도록 훅을 정의하고, resize 이벤트 시에도 반응하도록 debounce를 적용하여 불필요한 렌더링을 방지했습니다.

// useBreakpoint.ts
function useBreakpoint(): DeviceType {
  const [device, setDevice] = useState<DeviceType>(() =>
    getDeviceType(window.innerWidth),
  );

  useEffect(() => {
    const handleResize = debounce(() => {
      setDevice(getDeviceType(window.innerWidth));
    }, 200);

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return device;
}

export default useBreakpoint;
  1. 조건 기반 스크롤 제어 훅 구현 (useRemoveTopPageScroll)

condition과 observeDevices를 파라미터로 받아, 특정 디바이스 + 조건이 모두 만족될 때만 body에 overflow-hidden 클래스를 적용하거나 제거하도록 구현했습니다.

// useRemoveTopPageScroll.ts
interface UseRemoveTopPageScrollParams {
  condition: boolean;
  observeDevices: DeviceType[];
}

function useRemoveTopPageScroll({
  observeDevices,
  condition,
}: UseRemoveTopPageScrollParams) {
  const device = useBreakpoint();

  useEffect(() => {
    const deviceCondition = observeDevices.includes(device);
    if (condition && deviceCondition) {
      document.body.classList.add("overflow-hidden");
    } else {
      document.body.classList.remove("overflow-hidden");
    }
  }, [device, condition, observeDevices]);
}

export default useRemoveTopPageScroll;
// 실제 사용 예시
useRemoveTopPageScroll({
  observeDevices: ["mobile"], // mobile 크기인지 감시
  condition: showDropdown, // showDropdown이 true일 때, 최상단 엘리먼트의 스크롤이 제거 (overflow: hidden)
});
  1. 훅으로 추상화하여 전역 적용 가능하도록 설계 페이지나 모달 컴포넌트 내부에서 반복되지 않도록, 명확한 조건만 주면 어디서든 동일한 스크롤 제어 로직을 재사용할 수 있는 구조로 만들었습니다.

결과

  • 모바일 및 태블릿 디바이스에서 스크롤 이슈가 안정적으로 제거되어 사용자 인터랙션이 자연스럽게 유지됨
  • useRemoveTopPageScroll을 통해 페이지, 모달, 바텀시트 등 다양한 상황에 일관된 방식으로 스크롤 제어 가능
  • 스크롤 방지 조건이 명시적으로 드러나 유지보수가 쉬워졌고, 디바이스 대응 로직도 중앙에서 관리 가능해졌음

트러블 슈팅

컴포넌트

커스텀 훅

Clone this wiki locally