iOS React 웹뷰에서 스크롤 후 뒤로가기 시 발생하는 렌더링 오류

te-ing·2024년 3월 30일
0

◼︎ 문제상황

  1. iOS 웹뷰 환경의 리액트 특정 페이지에서 스크롤 후 뒤로가기 시, 스크롤 한 만큼의 영역에서 흰 화면이 나타나는 현상
  2. 조금이라도 스크롤을 하면 정상적으로 돌아옴
  3. 강제로 렌더링을 시도하거나 style을 변경해도 문제가 해결되지 않음

◼︎ 문제원인

뒤로가기 시, 기존의 스크롤 위치로 돌아오는 과정에서 페이지가 완전히 렌더링되지 않아 발생한 문제이다.
문제가 발생하는 페이지의 공통점은 로딩 이후 불러온 콘텐츠를 보여주는 방식이었고, Android는 뒤로가기 시 스크롤 위치를 불러오지 않기 때문에 Android 에서 해당 오류가 발생하지 않는 것이었다.


◼︎ 해결방안

1. 스크롤의 높이를 저장하지 않는다.

window.history.scrollRestoration = 'manual'; 을 사용하여 페이지 이동 시 스크롤의 위치를 저장하지 않도록 할 수 있다.
다만 특정 페이지에서만 일어나는 현상으로, window.scrollTo({top:0.1}); 와 같이 스크롤을 초기화 하는 방식을 사용하는 것을 권장한다.
0이 아닌 0.1을 사용하는 이유는 초기 렌더링 시에는 이미 scrollY가 0이기 때문에 scrollTo가 최적화되어 발생하지 않기 때문이다.

2. 페이지가 모두 렌더링 된 후 스크롤을 이동시킨다.

직접 스크롤 저장방식을 구현한 뒤, 목록을 불러오는 시점에 맞춰 스크롤을 이동시킨다.
다만 무한스크롤과 같이 사용자의 동작에 따라 스크롤 높이가 바뀌는 상황에서는 동작할 수 없으며, 별도의 로직이 필요하다.

아래는 스크롤 위치를 저장하고 불러올 수 있는 커스텀 훅이다.

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

/**
 * 스크롤 시마다 현재 스크롤 위치를 저장하고, 원하는 시점에 스크롤 위치를 불러올 수 있도록 하는 훅입니다.
 * 선언한 컴포넌트에서 스크롤 할 때마다 현재 스크롤 위치를 저장합니다.
 * @param ref 해당 컴포넌트가 렌더링 혹은 상태 변경 시 스크롤 위치를 불러옵니다.
 * @returns scrollRestore 실행 시 스크롤 위치를 불러옵니다.
 */
export default function useScrollRestoration(ref?: React.RefObject<HTMLElement>) {
  const { pathname } = useLocation();

  // 저장된 스크롤이 0이 아닐 때 스크롤을 불러온다.
  const scrollRestore = () => {
    if (getScrollStore(pathname)) {
      window.scrollTo({ top: getScrollStore(pathname) });
      setScrollStore(pathname, 0);
    }
  };

  useEffect(() => ref && scrollRestore(), [ref]);

  useEffect(() => {
    let currScrollY = 0;
    const scrollSaveEvent = () => {
      currScrollY = window.scrollY;
    };
    window.addEventListener("scroll", scrollSaveEvent);
    return () => {
      setScrollStore(pathname, currScrollY);
      window.removeEventListener("scroll", scrollSaveEvent);
    };
  }, [pathname]);

  return { scrollRestore };
}

const getScrollStore = (pathname: string): number => {
  const item = window.sessionStorage.getItem("scrollStore");
  if (!item) return 0;
  const scrollStore = JSON.parse(item);
  return scrollStore[pathname] || 0;
};

const setScrollStore = (pathname: string, scrollY?: number) => {
  const prevStore = window.sessionStorage.getItem("scrollStore") || "{}";
  const store = JSON.parse(prevStore);
  store[pathname] = scrollY ?? window.scrollY;
  window.sessionStorage.setItem("scrollStore", JSON.stringify(store));
  return store;
};
profile
프론트엔드 개발자

0개의 댓글