Front-end/React

[React] 채팅창 자동스크롤을 구현해보자

규투리 2023. 7. 12. 20:24
반응형

때는 바야흐로.. HourGoods 프로젝트에서 채팅창을 구현하던 중…

 

“새로운 메세지가 왔을 때 자동으로 스크롤이 내려가면 좋겠어요!” 와 같은 불편이 접수됐다.

기억을 정리하고자 블로그 작성을 해봤습니다!

아래는 결과화면입니다. (최종 완성본과는 다르지만.... 자동스크롤 녹화해둔게 없기에..)

1. 스크롤바를 항상 아래로...

일반적인 채팅창에서는

  1. 내가 채팅을 친 경우
  2. 상대방이 채팅을 친 경우

즉, 새로운 메세지가 올 때마다 스크롤이 항상 맨 아래로 향합니다.

자동 스크롤을 구현함으로써 채팅을 새로 칠 때마다 스크롤을 아래로 일일이 내릴 필요가 없으니, 사용자에게 보다 좋은 UX를 제공할 수 있게 됩니다.

 

2. 하단 영역을 useRef로 설정하기

잠깐 useRef에 대해 짚고 넘어가자면..

💡 useRef란?
특정 가상DOM 요소에 접근하기 위해 사용되는 React Hooks이다.

 

⚡️ useRef 사용 방법

1. 원하는 변수명으로 useRef() 선언
const chatBottomRef = useRef<HTMLDivElement | null>(null);​

2. 접근하고자 하는 DOM node에 ref 속성 추가
<div ref={chatBottomRef}></div>

3. 필요한 곳에서 변수명.current로 꺼내서 사용
  useEffect(() => {
    if (isScrollAtBottom) {
      scrollToBottom(chatBottomRef.current); // Bottom 영역 current 속성 할당
    }
    ...
  });

 

HourGoods 코드 발췌...

 

useRef 함수는 current 속성을 가지고 있는 객체를 반환합니다. 그렇기에 저희가 필요한 곳에서 .current로 꺼내서 사용할 수 있는 것이죠!

 

3. 지정한 영역으로 스크롤 끌어내리기

똑똑한 JS에는 내장함수 scrollIntoView 가 있습니다.

 

💡 element.scrollInToView 란?
scrollIntoView()가 호출된 요소가 사용자에게 표시되도록 요소의 상위 컨테이너를 스크롤한다.

 

⚡️ scrollInToView 사용 방법

element.scrollIntoView();

또한, 메서드의 파라미터값을 지정할 수 있습니다.

 

1. alignToTop (Boolean Parameter)

  • true : 해당 요소의 상단으로 스크롤 이동. {block: "start", inline: "nearest"}와 일치
  • false : 해당 요소의 하단으로 스크롤 이동. {block: "end", inline: "nearest"}와 일치

2. behavior (스크롤 애니메이션 지정 Parameter)

  • smooth : 스크롤 부드럽게 이동
  • instant : 스크롤이 단번에 즉시 이동

3. block (수직 정렬 Parameter)

  • start(위), center(중앙), end(아래), nearest 중 하나

4. inline (수평 정렬 Parameter)

  • start(왼쪽), center(중앙), end(오른쪽), nearest 중 하나

 

⚡️ scrollInToView 사용 예제

저의 경우에는 스크롤을 아래로 이동시키는 기능을 따로 함수로 빼서 구현하였습니다.

export const scrollToBottom = (element: HTMLDivElement | null) => {
  if (element) {
    element.scrollIntoView({
      behavior: "smooth",	// 부드럽게 이동
      block: "end",		// 아래로 이동(수직)
      inline: "nearest",	// 그대로(수평)
    });
  }
};

 

즉, useRef + scrollInToview 를 사용하게 되면,

지정한 영역이 사용자에게 표시되도록 스크롤을 이동시킬 수 있게 되는 것!!!!!!!!!

 

➡️ 코드전문

 

4. 추후에 보완하고 싶은 점...

새로운 채팅이 올라올 때마다 자동으로 스크롤이 내려가는 것까진 좋다! (그것이 UX니까)

 

하지만, 치명적인 단점을 발견했다..

 

위에 작성된 코드는.. 지정된 useRef에 새로운 이벤트가 발생했을 때 scrollToBottom() 함수가 실행되는 코드다.

그러다 보니, 내가 이전에 온 채팅들을 보려고 채팅창 스크롤을 올렸으나, 그 사이에 새로운 채팅이 입력되면 강제로 아래로 끌려가게 된다.

 

코드 전문을 보면 알 수도 있듯.. 사실 보완을 해보려 노력을 해보았다..ㅎㅎ..ㅠㅠ..

채팅창 전체를 useRef로 설정하여, 스크롤 이벤트가 감지가 됐을 때는 scrollToBottom() 함수가 실행이 되지 않도록하였다.

더보기
 // scroll 이벤트 감지하기
 useEffect(() => {
    if (chatMsgListRef.current) {
      chatMsgListRef.current.addEventListener("scroll", handleScroll);
    }
    return () => {
      if (chatMsgListRef.current) {
        chatMsgListRef.current.removeEventListener("scroll", handleScroll);
      }
    };
  }, [chatMsgListRef.current]);
  
...

// Bottom 영역으로부터 얼마나 떨어진 곳에서부터 scroll을 감지할 것인지
  const handleScroll = () => {
    const element = chatMsgListRef?.current;
    if (element) {
      const { scrollTop, scrollHeight, clientHeight } = element;
      const isScrolledToBottom =
        Math.abs(scrollTop + clientHeight - scrollHeight) <= 100;
      setIsScrollAtBottom(isScrolledToBottom);
    }
  };

 

또, 사용자의 스크롤이 Bottom 영역에 있지 않은 채 새로운 메세지가 오면

"이름 : 새로운 메세지"

와 같은 버튼을 만들어 하단으로 이동할 수 있도록 만들었다.

더보기
  const handleButtonClick = () => {
    // setIsNewMessage(false);
    setLatestMessage(null);
    scrollToBottom(chatBottomRef.current);
  };
  
  ...

{!isScrollAtBottom && isLatestMessage && (
                // 새메시지 하단 이동 버튼
                // 스크롤이 하단에 있지 않고 최신메세지가 있다면
                <button
                  type="button"
                  className="new-message-button"
                  onClick={handleButtonClick}
                >
                  <img
                    src={`https://d2uxndkqa5kutx.cloudfront.net/${message.imageUrl}`}
                    alt="프로필사진"
                  />
                  {message.nickname}: {message.content}
                </button>
              )}

 

하지만, 어째서인지 100% 정확하게 동작하지는 않는다..😅

 

코드 리팩토링 1순위이기에, 이번 여행이 끝난다면 반드시 블로그 포스팅을 해서 아래에 링크를 달아 새로운 주제로 업로드하겠다!!!!!

 

 

 

반응형