Skip to content

Commit 31c03a6

Browse files
committed
feat(posts): 스크롤 유지 추가
1 parent a71e65c commit 31c03a6

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

src/features/posts/Container.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { MainContainer } from '~/shared/components/MainContainer';
77
import { CategoryContainer } from './category/Container';
88
import { PostItem } from './components/Item';
99
import usePostListContainer from './hooks/usePostListContainer';
10+
import { useScrollRestoration } from './hooks/useScrollRestoration';
1011

1112
type PostListContainerProps = {
1213
posts: ServerPost[];
@@ -17,6 +18,7 @@ export const PostListContainer: FunctionComponent<PostListContainerProps> = ({
1718
}) => {
1819
const { categoryFilter, handleCategoryClick, handleClearClick } =
1920
usePostListContainer();
21+
useScrollRestoration();
2022

2123
return (
2224
<MainContainer className="md:py-[4.8rem]">

src/features/posts/components/Item.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import DateFormatter from '~/shared/components/DateFormatter';
88
export const PostItem: FunctionComponent<{
99
post: ServerPost;
1010
categoryFilter: Set<string>;
11+
onClick?: () => void;
1112
}> = ({
1213
post: {
1314
slug,
1415
frontmatter: { title, coverImage, description, date },
1516
category,
1617
},
1718
categoryFilter,
19+
onClick,
1820
}) => {
1921
const show = categoryFilter.size === 0 || categoryFilter.has(category);
2022
return (
@@ -26,6 +28,7 @@ export const PostItem: FunctionComponent<{
2628
{ 'h-0': !show },
2729
)}
2830
key={slug}
31+
onClick={onClick}
2932
>
3033
<Link
3134
className="flex flex-col items-center md:flex-row"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
'use client';
2+
3+
import { usePathname } from 'next/navigation';
4+
import { useLayoutEffect } from 'react';
5+
6+
const SCROLL_POSITIONS_KEY = 'scroll-positions';
7+
8+
const getScrollPositions = (): Map<string, number> => {
9+
if (typeof window === 'undefined') return new Map();
10+
11+
try {
12+
const stored = sessionStorage.getItem(SCROLL_POSITIONS_KEY);
13+
if (stored) {
14+
const parsed = JSON.parse(stored);
15+
return new Map(
16+
Object.entries(parsed).map(([key, value]) => [key, Number(value)]),
17+
);
18+
}
19+
} catch (error) {
20+
console.warn('Failed to load scroll positions from sessionStorage:', error);
21+
}
22+
23+
return new Map();
24+
};
25+
26+
const saveScrollPositions = (positions: Map<string, number>) => {
27+
if (typeof window === 'undefined') return;
28+
29+
try {
30+
const obj = Object.fromEntries(positions.entries());
31+
sessionStorage.setItem(SCROLL_POSITIONS_KEY, JSON.stringify(obj));
32+
} catch (error) {
33+
console.warn('Failed to save scroll positions to sessionStorage:', error);
34+
}
35+
};
36+
37+
export const useScrollRestoration = () => {
38+
const pathname = usePathname();
39+
40+
useLayoutEffect(() => {
41+
if (typeof window === 'undefined') return;
42+
43+
if ('scrollRestoration' in window.history) {
44+
window.history.scrollRestoration = 'manual';
45+
}
46+
47+
// 스크롤 점프 방지를 위한 CSS 조작
48+
const preventScrollJump = () => {
49+
const positions = getScrollPositions();
50+
const savedPosition = positions.get(pathname);
51+
52+
if (savedPosition !== undefined) {
53+
document.documentElement.style.scrollBehavior = 'auto';
54+
document.body.scrollTo(0, savedPosition);
55+
requestAnimationFrame(() => {
56+
document.documentElement.style.scrollBehavior = '';
57+
});
58+
}
59+
};
60+
61+
if (document.readyState === 'loading') {
62+
document.addEventListener('DOMContentLoaded', preventScrollJump);
63+
} else {
64+
preventScrollJump();
65+
}
66+
67+
// 페이지 이탈 시 현재 스크롤 위치 저장
68+
const handleBeforeUnload = () => {
69+
// NOTE: posts 기준으로 body에 스크롤이 있음. window.scrollY가 필요하다면 개선 필요
70+
const scrollY = document.body.scrollTop;
71+
const positions = getScrollPositions();
72+
positions.set(pathname, scrollY);
73+
saveScrollPositions(positions);
74+
};
75+
return () => {
76+
handleBeforeUnload();
77+
};
78+
}, [pathname]);
79+
};

0 commit comments

Comments
 (0)