useScrollRestoration
브라우저 또는 특정 엘리먼트의 이전 스크롤 위치를 저장하고 복원하는 커스텀 훅입니다.
기본적으로 window의 스크롤을 관리하며, ref를 사용하여 특정 스크롤 영역을 관리할 수 있습니다.
Code
Interface
typescript
interface UseScrollRestorationOptions {
id?: string;
enabled?: boolean;
behavior?: ScrollOptions['behavior'];
retry?: number;
}
function useScrollRestoration<T extends HTMLElement>({
id,
enabled,
behavior,
}?: UseScrollRestorationOptions): {
ref: React.RefObject<T | null>;
};
Options
| Name | Type | Default | Description |
|---|---|---|---|
id | string | 'window' 또는 'element' | 스크롤 복원 식별자 (다중 인스턴스 사용 시 필수) |
enabled | boolean | true | 스크롤 복원 활성화 여부 |
behavior | ScrollOptions['behavior'] | 'instant' | 스크롤 복원 동작 방식 ('auto', 'instant', 'smooth') |
retry | number | 5 | 스크롤 복원 재시도 횟수 (지수 백오프 적용) |
Remarks
주의사항
- 콘텐츠가 비동기적으로 로드되는 경우 혹은 이미지 로드로 인해 저장된 스크롤 위치보다 페이지 높이가 작을 수 있습니다.
- 이런 경우 자동으로 재시도하며,
지수 백오프(exponential backoff)방식으로 재시도 간격이 증가합니다. - 재시도 간격: 100ms → 200ms → 400ms → 800ms → 1600ms
- 기본 최대 재시도 횟수는
5회이며,retry옵션으로 조정할 수 있습니다.
- 이런 경우 자동으로 재시도하며,
- 한 컴포넌트 내에서 여러 번 훅을 사용할 경우, 각 인스턴스를 구분하려면
id옵션을 명시적으로 부여하세요.- 별도의
id를 지정하지 않으면,ref가 있는 경우'element', 없는 경우는'window'로 기본값이 설정됩니다. - id를 부여하지 않은 경우,
중복 key로 인해 스크롤 복원 동작이 정상적으로 동작하지 않을 수 있으니 주의가 필요합니다.
- 별도의
- 스크롤 위치 저장은
새로고침,페이지 이동(뒤로/앞으로가기),훅의 언마운트시점에 이루어집니다.- 따라서, 컴포넌트가 언마운트되지 않고 유지되는 구조에서 해당 훅을 호출 시 스크롤 복원 동작이 정상적으로 동작하지 않을 수 있으니 주의가 필요합니다. (예: Layout 컴포넌트)
- URL에
hash fragment(#section)가 있는 경우 스크롤 복원을 하지 않습니다.- 이는 hash 스크롤이라는 명확한 사용자 의도를 존중하고, 브라우저의 표준 동작과 충돌을 방지하기 위함입니다.
React Router 사용 시
- React Router는 자체적으로
location.key를 제공합니다. useScrollRestoration은 자동으로 이를 감지하여 사용합니다.- 별도 설정 없이도 페이지별 스크롤 위치가 정확히 복원됩니다.
Usage
Window Scroll
typescript
import { useScrollRestoration } from '@modern-kit/react';
const Page = () => {
useScrollRestoration();
return (
<div>
{/* 긴 컨텐츠... */}
</div>
);
};
Specific Element
typescript
import { useScrollRestoration } from '@modern-kit/react';
const ScrollBox = () => {
const { ref } = useScrollRestoration<HTMLDivElement>();
return (
<div
ref={ref}
style={{ height: '500px', overflowY: 'auto' }}
>
{/* 내부 스크롤이 발생하는 컨텐츠 */}
</div>
);
};
Multiple Instances
한 컴포넌트에서 여러 스크롤 영역을 관리할 때는 id를 명시적으로 부여해야 합니다.
typescript
import { useScrollRestoration } from '@modern-kit/react';
const MultiScrollPage = () => {
useScrollRestoration(); // window 스크롤 (기본 id: 'window')
const { ref: sidebarRef } = useScrollRestoration<HTMLDivElement>({ id: 'sidebar' });
const { ref: contentRef } = useScrollRestoration<HTMLDivElement>({ id: 'content' });
return (
<div>
<aside ref={sidebarRef} style={{ height: '100vh', overflowY: 'auto' }}>
{/* 사이드바 컨텐츠 */}
</aside>
<main ref={contentRef} style={{ height: '100vh', overflowY: 'auto' }}>
{/* 메인 컨텐츠 */}
</main>
</div>
);
};
Example (Window Scroll)
1. 전체 페이지 스크롤을 중간쯤 내리세요.
2. 브라우저의 '뒤로 가기' 버튼을 누르세요.
3. 스크롤 위치가 유지되어 있는지 확인하세요.
Example1 (특정 요소의 스크롤 복원)
특정 영역(예: 스크롤 가능한 컨테이너)의 스크롤 위치를 관리하는 기본 예제입니다.
ref를 사용하여 스크롤 위치를 복원할 요소를 지정합니다.
🧪 테스트 방법
- 아래 스크롤 박스를 중간쯤 내리세요 (예: "항목 10" 정도까지)
- 브라우저의 '뒤로 가기' 버튼을 클릭하세요
- 다시 돌아오면 스크롤 위치가 정확히 복원됩니다! ✨
📌 항목 1
📌 항목 2
📌 항목 3
📌 항목 4
📌 항목 5
📌 항목 6
📌 항목 7
📌 항목 8
📌 항목 9
📌 항목 10
📌 항목 11
📌 항목 12
📌 항목 13
📌 항목 14
📌 항목 15
📌 항목 16
📌 항목 17
📌 항목 18
📌 항목 19
📌 항목 20
💡 핵심:
ref를 통해 특정 요소의 스크롤 위치를 추적합니다.behavior: 'smooth' 옵션으로 부드러운 복원 애니메이션을 적용했습니다.Example2 (비동기 데이터 로딩 후 스크롤 복원)
실제 애플리케이션에서는 데이터가 비동기로 로드되는 경우가 많습니다.
이 예제는 데이터 로딩 중에도 스크롤 위치가 정확히 복원되는 것을 보여줍니다.
내부적으로 재시도 메커니즘(최대 5회, 지수 백오프)을 통해 콘텐츠가 완전히 로드될 때까지 기다립니다.
🧪 테스트 방법
- 데이터가 로딩될 때까지 기다리세요 (1초)
- 아래 스크롤 박스를 중간쯤 내리세요
- 브라우저의 '뒤로 가기' 버튼을 클릭하세요
- 다시 돌아오면:
- "Loading..." 표시 → 데이터 로딩 완료
- 자동으로 이전 스크롤 위치로 복원됩니다! 🎯
⏳ Loading...
💡 핵심: 콘텐츠 높이가 부족하면 자동으로 재시도합니다.
- 재시도 간격: 100ms → 200ms → 400ms → 800ms → 1600ms
- 최대 재시도: 5회 (기본값,
retry옵션으로 변경 가능)
Example3 (다중 인스턴스 사용)
한 페이지에서 여러 스크롤 영역을 독립적으로 관리해야 하는 경우, 각 인스턴스에 고유한 id를 부여해야 합니다.
예를 들어, 사이드바와 메인 콘텐츠 영역이 각각 독립적인 스크롤을 가지는 경우:
🧪 테스트 방법
- 파란색 영역(메인 콘텐츠)을 절반 정도 스크롤하세요.
- 초록색 영역(사이드바)도 절반 정도 스크롤하세요.
- 브라우저의 '뒤로 가기' 버튼을 누르세요.
- 다시 돌아오면 두 영역 모두 스크롤 위치가 정확히 복원됩니다! ✨
📄 메인 콘텐츠 (id: "main-content")
콘텐츠 항목 1
콘텐츠 항목 2
콘텐츠 항목 3
콘텐츠 항목 4
콘텐츠 항목 5
콘텐츠 항목 6
콘텐츠 항목 7
콘텐츠 항목 8
콘텐츠 항목 9
콘텐츠 항목 10
콘텐츠 항목 11
콘텐츠 항목 12
콘텐츠 항목 13
콘텐츠 항목 14
콘텐츠 항목 15
콘텐츠 항목 16
콘텐츠 항목 17
콘텐츠 항목 18
콘텐츠 항목 19
콘텐츠 항목 20
📑 사이드바 메뉴 (id: "sidebar-menu")
메뉴 항목 1
메뉴 항목 2
메뉴 항목 3
메뉴 항목 4
메뉴 항목 5
메뉴 항목 6
메뉴 항목 7
메뉴 항목 8
메뉴 항목 9
메뉴 항목 10
메뉴 항목 11
메뉴 항목 12
메뉴 항목 13
메뉴 항목 14
메뉴 항목 15
메뉴 항목 16
메뉴 항목 17
메뉴 항목 18
메뉴 항목 19
메뉴 항목 20
💡 포인트: 각 영역의 스크롤 위치가 독립적으로 저장되고 복원됩니다. 이는
id 옵션을 통해 각 인스턴스를 구분하기 때문입니다.