[RAPID-fix] 스크롤 sentinel 방식으로 교체: scrollIntoView useLayoutEffect (페인트 전 보장)

This commit is contained in:
2026-03-31 16:19:48 +09:00
parent 7b20edeacb
commit 0780410506

View File

@@ -23,7 +23,7 @@ export function ChatPanel({ room }: ChatPanelProps) {
const { emitTypingStart, emitTypingStop, typingUsers } = useMessengerSocket();
const scrollRef = useRef<HTMLDivElement>(null);
const [isAtBottom, setIsAtBottom] = useState(true);
const [scrollReady, setScrollReady] = useState(false);
const bottomRef = useRef<HTMLDivElement>(null);
const [isEditingName, setIsEditingName] = useState(false);
const [editName, setEditName] = useState("");
const editInputRef = useRef<HTMLInputElement>(null);
@@ -36,22 +36,14 @@ export function ChatPanel({ room }: ChatPanelProps) {
const lastMessageId = messages?.[messages.length - 1]?.id;
// useLayoutEffect: fires before browser paint → hide + scroll synchronously
// Scroll to bottom: sentinel scrollIntoView before paint (no visible jump)
useLayoutEffect(() => {
const el = scrollRef.current;
if (!el) return;
setScrollReady(false);
el.scrollTop = el.scrollHeight;
// Reveal after scroll is applied
requestAnimationFrame(() => setScrollReady(true));
bottomRef.current?.scrollIntoView();
}, [selectedRoomId, lastMessageId]);
// Second pass: re-scroll after async images load
// Second pass for async image loads
useEffect(() => {
const t = setTimeout(() => {
const el = scrollRef.current;
if (el) el.scrollTop = el.scrollHeight;
}, 600);
const t = setTimeout(() => { bottomRef.current?.scrollIntoView(); }, 600);
return () => clearTimeout(t);
}, [lastMessageId, selectedRoomId]);
@@ -167,7 +159,7 @@ export function ChatPanel({ room }: ChatPanelProps) {
</div>
{/* Messages */}
<div ref={scrollRef} className={`flex-1 min-h-0 overflow-y-auto relative${scrollReady ? "" : " invisible"}`}>
<div ref={scrollRef} className="flex-1 min-h-0 overflow-y-auto relative">
<div className="pt-2">
{messages?.map((msg, idx) => (
<div key={msg.id}>
@@ -196,6 +188,7 @@ export function ChatPanel({ room }: ChatPanelProps) {
<div className="px-4 h-5 flex items-center text-xs text-muted-foreground">
{roomTyping && roomTyping.length > 0 ? `${roomTyping.join(", ")}님이 입력 중...` : ""}
</div>
<div ref={bottomRef} />
</div>
</div>