TIL

2024.04.17 TIL #채팅 #스크롤 #스크롤다운 #조건부

inz1234 2024. 5. 8. 13:03

📌 Task TODOLIST

- [x] 스크롤할 div 선정하기

- [x] ref 부여하기

- [x] onScroll props로 스크롤 함수 넣기

- [x] 스크롤 다운하기


개발 내용

1. 스크롤할 div 선정하기

2. ref 부여하기

3. onScroll props로 스크롤 함수 넣기

 

How(과정) ?

(1) 스크롤에 관여하는 요소들이 몇 가지 있었는데, scrollBox를 scrollRef 를 준 것의 current 값, 즉 scrollRef.current라고 할때, 

scrollBox.scrollTop

- 스크롤 바가 현재 제일 최상단으로부터 얼마나 떨어져 있는지

scrollBox.scrollHeight

- 스크롤 가능한 영역의 전체 높이

- 내용이 스크롤 밖으로 넘치는 경우 뷰포트의 길이보다 커질 수 있음 / 항상 일정 

scrollBox.clientHeight

- 콘텐츠가 보여지는 영역의 높이

- 뷰포트 

 

(2) 이 세 개의 개념들을 활용해서 현재 스크롤 중임을 

const isScroll = scrollBox.scrollTop < scrollBox.scrollHeight - scrollBox.clientHeight - SCROLL_GAP;

이렇게 표현할 수 있다. 

여기서 SCROLL_GAP이란 5라는 상수인데, 스크롤 중을 판단하는 임계값으로 사용된다.

즉, 만약 SCROLL_GAP이 0 이라면 사용자가 스크롤 맨 아래에 도달했을 때만 스크롤 중으로 판단이 되는데,

내가 원하는 것은 스크롤이 맨 위/아래에 도달하지 않을 때를 스크롤 중으로 하고 싶기 때문에 일정 값을 빼는 것이다.

5가 아닌 10 또는 그 이상으로 해도 된다.

 

(3) 하여 내가 만든 스크롤 함수 handleScroll을 scrollRef와 함께 스크롤 할 영역에 props로 넘긴다.

// 스크롤 이벤트가 발생할 때
  const handleScroll = () => {
    const scrollBox = scrollRef.current;
    if (scrollBox) {
      const isScroll = scrollBox.scrollTop < scrollBox.scrollHeight - scrollBox.clientHeight - SCROLL_GAP;
      setIsScrolling(isScroll);
      if (!isScroll) {
        setNewAddedMsgNum(0);
      }
      setIsScrollTop(scrollBox.scrollTop === 0);
    }
  };

 ㄴ setIsScrollTop은 채팅검색에 필요한 요소다.

  return (
    <>
      <div
        className={'w-full h-full flex-1 p-[16px] flex flex-col gap-[8px] overflow-y-auto scroll-smooth'}
        ref={scrollRef}
        onScroll={handleScroll}
      >      
      ...

 

4. 스크롤 다운 하기

Why(이유) ?

채팅을 보고 있다가 최하단으로 내려가는 버튼을 만들고 싶었다.

또한, 채팅을 보고 있다가 새로운 메세지가 추가되면 "새로운 메세지 0개 추가" 라는 버튼도 조건부로 만들고 싶었다.

 

How(과정) ?

위의 설명을 토대로 스크롤 다운을 구현했다. 

scrollTop이 scrollHeight 와 같아지면 현재 스크롤바의 위치가 전체 높이와 같아지므로 스크롤 다운이 된다.

 const handleScrollDown = () => {
    scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  };

이 스크롤 다운 함수를 최하단으로 내리는 버튼을 생성해서 props로 넘긴다.

나는 1. 스크롤 중이고 2. 새로 추가된 메세지 수가 0개인지의 조건으로

그냥 스크롤 다운 버튼을 할지, 새로운 메세지 추가버튼을 할지 조건부로 렌더링했다.

{isScrolling ? (
        newAddedMsgNum === 0 ? (
          <ChatScroll handleScrollDown={handleScrollDown} />
        ) : (
          <NewChatAlert
            newAddedMsgNum={newAddedMsgNum}
            handleScrollDown={handleScrollDown}
            setNewAddedMsgNum={setNewAddedMsgNum}
          />
        )
      ) : (
        <></>
      )}

 

const ChatScroll = ({ handleScrollDown }: { handleScrollDown: () => void }) => {
  return (
    <>
        <div className="absolute bottom-28 w-full" id="스크롤 내리는 버튼">
          <FaArrowCircleDown
            className="w-12 h-12 text-[#E4D4F4] mx-auto cursor-pointer hover:scale-105 transition-all ease-in-out animate-bounce"
            onClick={handleScrollDown}
          />
        </div>
    </>
  );
};

export default ChatScroll;
const NewChatAlert = ({
  newAddedMsgNum,
  handleScrollDown,
  setNewAddedMsgNum
}: {
  newAddedMsgNum: number;
  handleScrollDown: () => void;
  setNewAddedMsgNum: Dispatch<SetStateAction<number>>;
}) => {
  const handleNewMsgAlertScroll = () => {
    handleScrollDown();
    setNewAddedMsgNum(0);
  };

  return (
    <div className="absolute bottom-28 w-full">
      <div className="flex mx-auto w-full cursor-pointer" onClick={handleNewMsgAlertScroll}>
        <div className="flex gap-[6px] mx-auto my-auto px-[16px] py-[14px] bg-[#F2EAFA] rounded-lg font-bold text-lg text-mainColor font-semibold">
          <div className="my-auto">
            <FaChevronDown />
          </div>
          <h1>{newAddedMsgNum} New Message</h1>
        </div>
      </div>
    </div>
  );
};

export default NewChatAlert;

 

🚨  TroubleShotting

-

그럴 듯한 트러블슈팅은 없었다. 생소한 개념이 어려웠을 뿐..

📸 스크린샷

스크롤 중 + 새로운 메세지 수 === 0 스크롤 중 + 새로운 메세지가 추가됐을 때
스크롤 다운

 

📚 레퍼런스

스크롤 개념 정리를 너무 잘해두셨다.

https://devbirdfeet.tistory.com/228

 

스크롤 바 꾸미기

https://www.geeksforgeeks.org/how-to-change-style-of-scrollbar-using-tailwind-css/