팀 프로젝트 진행 중 채팅 봇 개발을 위해 채팅창을 퍼블리싱 하고 있었는데 채팅이 많아져 스크롤이 생겼을 때 항상 최신의 채팅 내역을 보여주기 위해 채팅창의 최하단을 항상 보여주고 싶어서 이것저것 알아봤는데 내가 떠올리지 못했던 방향으로 해결 하게 되어 포스팅을 하게됐다.
만든 채팅창 모습
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를 찾아가는 기능을 만드는 등의 확장성에 있어서 해당 메서드를 좀 더 높게 평가했고 이번엔 그것을 위한 경험을 해봤다는 것에 의의를 뒀다. 작은 채팅창 하나를 만드는데도 고려 해야 할 부분이 참 많다는 생각이 든다.
Ghost