Skip to content

Commit 3446fa0

Browse files
authored
Merge pull request #511 from solid-connection/fix/chat-initial-scroll-bottom
fix(web): 채팅 상세 진입 시 하단 고정 스크롤 개선
2 parents 49ba3d3 + 1f9f585 commit 3446fa0

2 files changed

Lines changed: 69 additions & 5 deletions

File tree

apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { type ChatMessage, ConnectionStatus } from "@/types/chat";
66
// --- 프로젝트 내부 의존성 ---
77
import useInfinityScroll from "@/utils/useInfinityScroll";
88

9+
const BOTTOM_PROXIMITY_THRESHOLD = 80;
10+
911
const getMessageDedupeKey = (message: ChatMessage): string => {
1012
if (message.id > 0) {
1113
return `id:${message.id}`;
@@ -22,6 +24,9 @@ const useChatListHandler = (chatId: number) => {
2224
// --- 1. State 및 Ref 선언 ---
2325
const clientRef = useRef<Client | null>(null);
2426
const messagesEndRef = useRef<HTMLDivElement>(null); // 새 메시지 수신 시 자동 스크롤을 위한 ref
27+
const scrollContainerRef = useRef<HTMLDivElement>(null); // 실제 스크롤 컨테이너 ref
28+
const hasInitialAutoScrolledRef = useRef(false);
29+
const prevMessageCountRef = useRef(0);
2530

2631
// --- 2. 하위 Hooks 호출 ---
2732

@@ -71,13 +76,69 @@ const useChatListHandler = (chatId: number) => {
7176
}
7277
}, [chatHistoryPages, setSubmittedMessages]);
7378

74-
// 새로운 메시지가 추가되었을 때, 스크롤을 대화 목록의 맨 아래로 이동시킵니다.
79+
// 채팅방 전환 시 자동 스크롤 상태를 초기화합니다.
80+
useEffect(() => {
81+
hasInitialAutoScrolledRef.current = false;
82+
prevMessageCountRef.current = 0;
83+
}, [chatId]);
84+
85+
// 초기 히스토리 로딩 완료 후, 최초 1회만 하단으로 이동합니다.
7586
useEffect(() => {
76-
// 이전 기록을 불러오는 중일 때는 자동 스크롤을 방지하여 사용자 경험을 해치지 않습니다.
77-
if (!isFetchingNextPage && messagesEndRef.current) {
78-
messagesEndRef.current.scrollIntoView();
87+
if (isLoading || isFetchingNextPage || submittedMessages.length === 0 || hasInitialAutoScrolledRef.current) {
88+
return;
89+
}
90+
91+
const rafId = requestAnimationFrame(() => {
92+
const container = scrollContainerRef.current;
93+
if (!container) return;
94+
95+
container.scrollTop = container.scrollHeight;
96+
hasInitialAutoScrolledRef.current = true;
97+
prevMessageCountRef.current = submittedMessages.length;
98+
});
99+
100+
return () => cancelAnimationFrame(rafId);
101+
}, [isLoading, isFetchingNextPage, submittedMessages.length]);
102+
103+
// 신규 메시지 도착 시, 사용자가 하단 근처에 있을 때만 자동으로 하단을 유지합니다.
104+
useEffect(() => {
105+
if (isLoading || isFetchingNextPage) return;
106+
107+
const currentMessageCount = submittedMessages.length;
108+
const prevMessageCount = prevMessageCountRef.current;
109+
const container = scrollContainerRef.current;
110+
111+
if (!container) {
112+
prevMessageCountRef.current = currentMessageCount;
113+
return;
79114
}
80-
}, [isFetchingNextPage]);
115+
116+
if (currentMessageCount <= prevMessageCount) {
117+
prevMessageCountRef.current = currentMessageCount;
118+
return;
119+
}
120+
121+
if (!hasInitialAutoScrolledRef.current) {
122+
prevMessageCountRef.current = currentMessageCount;
123+
return;
124+
}
125+
126+
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
127+
128+
if (distanceFromBottom <= BOTTOM_PROXIMITY_THRESHOLD) {
129+
const rafId = requestAnimationFrame(() => {
130+
const target = scrollContainerRef.current;
131+
if (!target) return;
132+
target.scrollTop = target.scrollHeight;
133+
});
134+
135+
prevMessageCountRef.current = currentMessageCount;
136+
137+
return () => cancelAnimationFrame(rafId);
138+
}
139+
140+
prevMessageCountRef.current = currentMessageCount;
141+
}, [isLoading, isFetchingNextPage, submittedMessages.length]);
81142

82143
// --- 4. Handler 함수 ---
83144

@@ -197,6 +258,7 @@ const useChatListHandler = (chatId: number) => {
197258
isFetchingNextPage, // 이전 기록 로딩 상태
198259

199260
// Refs
261+
scrollContainerRef, // 실제 스크롤 컨테이너 ref
200262
messagesEndRef, // 자동 스크롤을 위한 ref
201263
topDetectorRef, // 무한 스크롤 감지를 위한 ref
202264

apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => {
4040
isFetchingNextPage, // 이전 기록 로딩 상태
4141

4242
// Refs
43+
scrollContainerRef, // 실제 스크롤 컨테이너 ref
4344
messagesEndRef, // 자동 스크롤을 위한 ref
4445
topDetectorRef, // 무한 스크롤 감지를 위한 ref
4546

@@ -111,6 +112,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => {
111112
</div>
112113
{/* 채팅 메시지 영역 - 항상 스크롤 가능, 스크롤바 숨김 */}
113114
<div
115+
ref={scrollContainerRef}
114116
className="scrollbar-hide mt-4 flex-1 overflow-y-auto p-4 pb-6"
115117
style={{
116118
scrollbarWidth: "none" /* Firefox */,

0 commit comments

Comments
 (0)