@@ -6,6 +6,8 @@ import { type ChatMessage, ConnectionStatus } from "@/types/chat";
66// --- 프로젝트 내부 의존성 ---
77import useInfinityScroll from "@/utils/useInfinityScroll" ;
88
9+ const BOTTOM_PROXIMITY_THRESHOLD = 80 ;
10+
911const 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
0 commit comments