개발일기/Web

[React] 모바일 웹 모달 스크롤 동작 막기, 뒤로가기 동작 제어하기 (feat. 훅 만들기)

DongKeun2 2024. 11. 18. 11:02

 모바일 웹에서 모달을 사용하면서 마주했던 문제에 대해 정리를 해두려 한다.

두 가지 문제가 있었는데, 모달을 띄웠을 때 뒷배경이 스크롤 되는 문제와 뒤로 가기 시 브라우저가 닫혀버리는 문제이다.

 

스크롤 동작 막기

뒷 배경이 스클로 되는 문제는 웹에서도 자주 다루기 때문에 간단하게 넘어가겠다.

 

이벤트 버블링으로 인해 스크롤 이벤트가 body까지 전달되어 스크롤이 되는 현상으로 body에서 이를 제어해주기만 하면 된다.

{isOpen && <MyModal onClose={handleModalOpen} />}

 예를 들어 open state를 통해 렌더되는 모달이 있다 가정한다. (handleModalOpen은 setOpen 제어 함수이다.)

open이 true가 될 때 body의 스크롤 동작을 막아주고, false가 될 때 다시 열어주면 될 것 같다.

 

useEffect(() => {
    if (isOpen) document.body.style.overflow = 'hidden';
    else document.body.style.removeProperty('overflow');
}, [oepn]);

이렇게 하면 간단하게 모달 바깥의 스크롤을 막아줄 수 있다.

 

여러 컴포넌트에서 사용할 생각이라면 이렇게 훅으로 만들어도 된다.

import { useEffect } from 'react';

const useScrollLock = (isLocked) => {
  useEffect(() => {
    if (isLocked) document.body.style.overflow = 'hidden';
    else document.body.style.removeProperty('overflow');

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

export default useScrollLock;

 

 

 

 

뒤로가기 동작 제어하기

 웹에서는 모달을 닫기 위해 뒤로 가기 동작을 잘 하지 않아 컨트롤 할 일이 거의 없지만, 모바일에서는 모달을 닫기 위해 뒤로 가기를 누르는 패턴은 자연스럽다. 이걸 별도로 처리해주지 않으면 브라우저에서 이전 페이지로 동작하거나 닫혀버리기 때문에 제어가 필요하다.

 

 약간의 편법을 사용하여 이걸 해결할 수 있다. 모달이 열릴 때 브라우저 히스토리를 강제로 하나 추가해준다. 그럼 뒤로 가기 시 이전 페이지로 이동하는 대신 내가 강제로 주입한 히스토리 하나를 pop하게 된다. 그리고 다른 동작으로 모달이 닫힐 때도 이걸 제거해주면 된다.

 

 뒤로 가기를 제어하는 popstate 이벤트를 등록해준다.

useEffect(() => {
    const handlePopState = () => setIsOpen(false);

    window.addEventListener('popstate', handlePopState);
    return () => {
      window.removeEventListener('popstate', handlePopState);
    };
}, []);

 

이렇게 하면 뒤로가기 시 state를 변경시켜 모달을 닫게 할 수 있고, 

  const handleModal = useCallback((open) => {
    if (open) window.history.pushState(null, '');
    else window.history.replaceState(null, '');
    setIsOpen(open);
  }, []);

해당 함수를 통해 히스토리를 주입하고, 제외할 수 있다

useEffect를 사용하지 않은 이유는 뒤로 가기 동작으로 state 변화가 일어났을 때는 히스토리를 빼 줄 필요가 없기 때문이다.

 

 보통 모달을 이중으로 설계하는 경우는 많이 없기 때문에 이것도 훅으로 만들어서 사용했다.

import { useState, useEffect, useCallback } from 'react';

function useModalWithHistory(initialState = false) {
  const [isOpen, setIsOpen] = useState(initialState);

  const handleOpen = useCallback((open) => {
    if (open) window.history.pushState(null, '');
    else window.history.replaceState(null, '');
    setIsOpen(open);
  }, []);

  useEffect(() => {
    const handlePopState = () => setIsOpen(false);

    window.addEventListener('popstate', handlePopState);
    return () => {
      window.removeEventListener('popstate', handlePopState);
    };
  }, []);

  return [isOpen, handleOpen];
}

export default useModalWithHistory;

 

 이렇게 설계하면 모바일 웹에서 뒤로 가기 동작을 쉽게 제어할 수 있다.

  const [isOpen, handleOpen] = useModalWithHistory(false);

 

 이렇게 사용할 수 있고, 이제 브라우저 히스토리를 생각하지 않고 handleOpen을 통해 모달을 여닫아 주면 된다.