뒤로가기 시, 기존의 스크롤 위치로 돌아오는 과정에서 페이지가 완전히 렌더링되지 않아 발생한 문제이다.
문제가 발생하는 페이지의 공통점은 로딩 이후 불러온 콘텐츠를 보여주는 방식이었고, Android는 뒤로가기 시 스크롤 위치를 불러오지 않기 때문에 Android 에서 해당 오류가 발생하지 않는 것이었다.
window.history.scrollRestoration = 'manual';
을 사용하여 페이지 이동 시 스크롤의 위치를 저장하지 않도록 할 수 있다.
다만 특정 페이지에서만 일어나는 현상으로, window.scrollTo({top:0.1});
와 같이 스크롤을 초기화 하는 방식을 사용하는 것을 권장한다.
0이 아닌 0.1을 사용하는 이유는 초기 렌더링 시에는 이미 scrollY가 0이기 때문에 scrollTo가 최적화되어 발생하지 않기 때문이다.
직접 스크롤 저장방식을 구현한 뒤, 목록을 불러오는 시점에 맞춰 스크롤을 이동시킨다.
다만 무한스크롤과 같이 사용자의 동작에 따라 스크롤 높이가 바뀌는 상황에서는 동작할 수 없으며, 별도의 로직이 필요하다.
아래는 스크롤 위치를 저장하고 불러올 수 있는 커스텀 훅이다.
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;
};