모달이면서 동시에 페이지여야 하는… 스크롤 이슈

정혜인·2025년 2월 24일
0

이 프로젝트는 https://uxpirates.xyz/ 에 대한 내용입니다!

진행했던 프로젝트에서 화면은 모달창으로 띄워야하는데, 각 모달창에서 링크 공유를 누르면 링크가 공유되고, 공유된 링크에 접속하면 그 모달창이 그대로 띄워져 있어야 하는 기능을 구현해야 했습니다.

결론적으로 제가 설계한 방식은, 홈 화면에 있는 여러개의 미리보기 컴포넌트 중 하나를 클릭하면 해당 컴포넌트의 id를 param으로 가지는 url로 이동하도록 하는 것이었습니다.

겉으로 보기에는 페이지 이동이 아닌 모달창으로 보여져야 했기 때문에, url은 이동시키되 화면은 모달로 구현해야 했던 것이었는데,

아래 gif에서 볼 수 있는 것처럼 Home 컴포넌트에서 여러 개의 ColumnPreview를 클릭하면 모달 형태로 세부 정보를 표시하고, URL이 /detail/:id 형식으로 변경되도록 구현해주었습니다.

하지만 크게 3가지의 문제가 발생했는데,

  1. 모달을 열 때 스크롤이 맨 위(0)로 이동하는 문제가 발생.
  2. 모달을 닫을 때 스크롤 위치를 복원해야 했으나, 부자연스럽거나 예상치 못한 위치로 이동하는 경우가 있었습니다.
  3. 모달이 열려 있는 동안에는 배경 스크롤이 고정되어야 했습니다.

이 문제를 해결하기 위해 여러 가지 시도를 해보았고, 결국 어떻게 설계하고 구현했는지를 이번 포스팅에서 작성해보려 합니다.


1️⃣ 스크롤 위치를 저장하고 복원하는 기본 구조 설계

가장 먼저 시도한 방법은 모달을 열기 전에 현재 스크롤 위치를 저장하고, 모달을 닫을 때 저장된 위치로 복원하는 방식이었습니다. 이를 위해 window.scrollY 값을 저장하고 복원하는 코드로 다음과 같이 구현했습니다:

const savedScrollTop = useRef(0);

const handleOpenModal = () => {
  savedScrollTop.current = window.scrollY; // 현재 스크롤 위치 저장
  setIsOpenedDetailModal(true);
};

const handleCloseModal = () => {
  setIsOpenedDetailModal(false);
  setTimeout(() => {
    window.scrollTo({ top: savedScrollTop.current, behavior: "smooth" }); // 스크롤 복원
  }, 0);
};

문제점

  • URL 변경(pushState) 후 DOM이 리렌더링되면서 브라우저가 스크롤을 0으로 강제로 이동.
  • 모달을 열 때 스크롤이 0으로 이동한 후 다시 저장된 위치로 돌아가는 이상한 동작 발생.

2️⃣ CSS로 스크롤 고정 및 해제 구현

모달이 열려 있을 때 배경 스크롤을 막기 위해 document.bodyoverflow 스타일을 조정했습니다:

useEffect(() => {
  if (isOpenedDetailModal) {
    document.body.style.overflow = "hidden"; // 배경 스크롤 막기
  } else {
    document.body.style.overflow = "auto"; // 배경 스크롤 복원
  }
}, [isOpenedDetailModal]);

문제점

  • 배경 스크롤을 막는 데는 성공했으나, overflow: hidden만으로는 스크롤 위치를 유지할 수 없었음.
  • 특히 모바일 환경에서는 스크롤 위치가 초기화되는 문제가 여전히 발생.

3️⃣ 라우터 변경 후 스크롤 위치 유지

모달을 열 때 URL이 변경되므로, URL 변경 후 스크롤 위치를 강제로 복원하는 방식도 시도했습니다:

useEffect(() => {
  if (selectedDetailId) {
    // 모달 열기 시 기존 위치 유지
    setTimeout(() => {
      window.scrollTo({ top: savedScrollTop.current });
    }, 0);
  }
}, [selectedDetailId]);

문제점

  • URL 변경이 발생한 후 DOM 리렌더링 타이밍과 스크롤 복원 타이밍이 충돌.
  • 일부 환경(특히 모바일)에서는 스크롤이 여전히 0으로 이동하거나, 부자연스러운 위치로 이동.

4️⃣ style.top을 활용한 고정

위 시도들에서 발생한 문제를 근본적으로 해결하기 위해, 스크롤 위치를 저장한 후 document.body의 스타일 속성을 활용하여 스크롤을 고정하고 복원하는 방식을 최종적으로 선택했습니다.

  1. 모달 열기
    • 현재 스크롤 위치를 window.scrollY로 저장.
    • document.body.style.positionfixed로 설정하여 스크롤을 고정.
    • style.top에 현재 스크롤 값을 음수로 설정하여 고정된 위치에서도 화면이 움직이지 않도록 처리.
  2. 모달 닫기
    • document.body.style.positionstyle.top 값을 초기화하여 원래 상태로 복원.
    • 저장된 스크롤 값을 활용해 window.scrollTo로 복원.

여러 방식을 거친 끝에 최종적으로 배포된 코드의 일부입니다!

useEffect(() => {
  if (isOpenedDetailModal || isMobileMenuOpen) {
    const scrollY = window.scrollY; // 현재 스크롤 위치 저장
    document.body.style.position = "fixed";
    document.body.style.top = `-${scrollY}px`;
    document.body.style.left = "0";
    document.body.style.right = "0";
    document.body.style.width = "100%";
  } else {
    const scrollY = parseInt(document.body.style.top || "0", 10) * -1; // 스크롤 복원
    document.body.style.position = "";
    document.body.style.top = "";
    window.scrollTo({ top: scrollY, behavior: "auto" });
  }
}, [isOpenedDetailModal, isMobileMenuOpen]);

결론적으로 모달을 열고 닫을 때 스크롤 위치가 유지되었고, 열기 전/후의 위치를 완벽하게 유지시킬 수 있었습니다.

그리고 배경 스크롤을 고정해주었고, 모바일 환경에서도 문제 없이 잘 동작하는 것을 확인할 수 있었습니다!!

최종 구현된 모습은 아래 gif와 같습니다.

0개의 댓글