팀 프로젝트 진행 중 채팅 봇 개발을 위해 채팅창을 퍼블리싱 하고 있었는데 채팅이 많아져 스크롤이 생겼을 때 항상 최신의 채팅 내역을 보여주기 위해 채팅창의 최하단을 항상 보여주고 싶어서 이것저것 알아봤는데 내가 떠올리지 못했던 방향으로 해결 하게 되어 포스팅을 하게됐다.

만든 채팅창 모습
import React, { useState, useEffect, useRef } from "react";
const AiChatBtn: React.FC<AiChatBtnProps> = ({
onClick,
children,
className = "",
}) => {
// 타 상수값들 생략
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(scrollToBottom, [messages]);
//handleSendMessage 코드 생략
return (
<div>
<div className="bg-white rounded-lg shadow-xl fixed bottom-24 right-10 w-[300px] h-[480px] flex flex-col">
// 채팅창 헤더 jsx 코드
<div className="flex-grow p-4 overflow-y-auto bg-gray-100">
// 채팅창 jsx 코드
<div ref={messagesEndRef} /> {/* 스크롤을 위한 참조 요소 useRef 이용 */}
</div>
// input창 jsx 코드
/>
<button
//button jsx 코드
</button>
</div>
);
};
export default AiChatBtn;
useRef
동적인 ui를 만들다보면 특정 요소에만 접근하고 싶은 순간이 있다. tailwind css를 사용하기 전에는 className을 이용하여 접근하곤 했는데 tailwind css를 사용한 후로는 useRef라는 훅을 이용하여 div에 id 값을 매기듯이 지정한 후 참조하는 방식을 사용하고 있다.
위에 제시한 코드에서는 <div ref={messagesEndRef} />로 ref에 이 빈 div를 참조시켰다. 왜 빈 div를 참조시켰는지는 후에 설명하는 다른 내용들을 보면 알 수 있을 것이다.
scrollIntoView()
이번 주제로 포스팅을 하게 된 이유인데 그동안은 window.scrollTo() 이라던가 element.scrollTop 같은 메서드를 사용해왔는데 이번에는 scrollIntoView()를 사용하였다. 이 메서드에 대해 좀 더 알아보자
기본 사용법
element.scrollIntoView(options); 로 사용하고 option 값의 구성은
behavior: 스크롤 애니메이션을 정의한다.
- "auto" (기본값): 즉시 스크롤
- "smooth": 부드러운 애니메이션과 함께 스크롤
block: 요소의 수직 정렬을 정의한다.
- "start": 요소를 뷰포트의 상단에 맞춘다.
- "center": 요소를 뷰포트의 중앙에 맞춘다.
- "end": 요소를 뷰포트의 하단에 맞춘다.
- "nearest" (기본값): 가장 가까운 위치로 스크롤한다.
라는 하나의 메서드에서 옵션값만을 변경하여 다양하게 활용할 수 있다는 점을 알게되어 이번에 사용하게 되었고 그 중에서도 기본값이 nearest를 활용하기 위하여 채팅창의 최하단에 height가 0 인 빈 div를 선언하고 그것을 참조하는 ref를 만들어 해당 ref에 가까운 위치 즉 채팅창의 최하단으로 이동되게끔 하였다. 그리고 smooth 같은 behavior 값을 사용하면 transition을 적용한 것처럼 작동하여 사용자 경험도 쉽게 향상 시킬 수 있었다.
마치며
그래서 결국 기능이 채팅창의 최하단으로 스크롤을 고정시키는 것이라면 앞서 소개했던 두 개의 메서드가 직관성 측면에선 더 좋은게 아니냐는 생각이 들기도 했는데, useRef와 view를 중심으로 한 scrollIntoView() 를 활용하면 차후에 특정 내용이 담긴 div를 찾아가는 기능을 만드는 등의 확장성에 있어서 해당 메서드를 좀 더 높게 평가했고 이번엔 그것을 위한 경험을 해봤다는 것에 의의를 뒀다. 작은 채팅창 하나를 만드는데도 고려 해야 할 부분이 참 많다는 생각이 든다.