React

상단 이동 버튼 & 스크롤 시 버튼 나타나기

minsun309 2024. 8. 23. 11:20

포스트 글이 길거나 블로그, 프로젝트 갯 수가 많아지면 스크롤이 발생해 상단 이동 버튼을 배치하는게 좋을 것 같아 추가해보았다.

 

상단 이동 버튼

window.scrollTo(xpos, ypos, behavior:'auto'}) ****를 사용하면 원하는 위치로 이동 시킬 수 있다.

behavior의 값에는 auto, instant, smooth가 있다. (문자이므로 따옴표가 필요하다.)

auto는 기본 값이며, 바로 위치로 이동한다. instant도 같은 동작을 한다.

smooth는 부드럽게 이동하는 애니메이션 효과를 보여준다.

⇒ 부드럽게 상단 위치하기 위해서 클릭 시 window.scrollTo({ top: 0, behavior: "smooth" }); 실행되게 했다.

import { useEffect, useState } from "react";
import { HiChevronUp } from "react-icons/hi";

export default function MoveToTop() {
  const moveToTop = () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  return (
    <div
      onClick={moveToTop}
      className="fixed bottom-7 right-4 lg:right-10 p-2 z-20 cursor-pointer hover:scale-90 transition-all duration-500 rounded-full bg-[#2c82f2]"
    >
      <HiChevronUp className="text-2xl text-white" />
    </div>
  );
}

 

상단 이동 버튼 + 일정 스크롤 높이에서 등장

추가로 스크롤 브라우저 최 상단에서는 상단 이동 버튼이 필요 없기 때문에 일정 스크롤 높이에서 부터 상단 이동 버튼이 등장했으면 좋을 것 같았다.

useState 를 활용해 버튼의 상태 값을 css에 적용했다.

showTopBtn ? "opacity-100 visible" : "opacity-0 invisible" showTopBtn가 true면 보이게 적용했다.

*visible 는 visibility: visible; / invisible 는 visibility: hidden; (tailwind 표기)

이제 필요한 거는 원하는 시점에서 showTopBtn가 true로 변하게 해야 됩니다.

  • useEffect 훅을 활용하여 컴포넌트가 마운트될 때 실행됩니다.
  • 스크롤 이벤트가 발생할 때마다 호출 되는 showBtnClick 함수가 정의합니다.
  • showBtnClick 함수 내에서 현재 스크롤 위치 (window.scrollY) 를 확인하고, 위치가 400보다 크면setShowTopBtn(true)를 호출하고 그렇지 않으면 setShowTopBtn(false) 를 호출합니다.
  • window.addEventListener("scroll", showBtnClick);를 통해 스크롤 이벤트에 대한 리스너가 등록되어 스크롤이 발생할 때마다 showBtnClick 함수가 호출됩니다.
  • 컴포넌트가 언마운트되면 return 블록 내의 함수가 실행되어 window.removeEventListener("scroll", showBtnClick); 를 통해 등록된 스크롤 이벤트 리스너를 제거하여 메모리 누수를 방지합니다.
 useEffect(() => {
    const showBtnClick = () => {
      if (window.scrollY > 400) {
        setShowTopBtn(true);
      } else {
        setShowTopBtn(false);
      }
    };
    window.addEventListener("scroll", showBtnClick);
    return () => {
      window.removeEventListener("scroll", showBtnClick);
    };
  }, []);

 

 

전체 코드

import { cls } from "libs/utils";
import { useEffect, useState } from "react";
import { HiChevronUp } from "react-icons/hi";

export default function MoveToTop() {
  const [showTopBtn, setShowTopBtn] = useState(false);
  const moveToTop = () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  };
  useEffect(() => {
    const showBtnClick = () => {
      if (window.scrollY > 400) {
        setShowTopBtn(true);
      } else {
        setShowTopBtn(false);
      }
    };
    window.addEventListener("scroll", showBtnClick);
    return () => {
      window.removeEventListener("scroll", showBtnClick);
    };
  }, []);
  return (
    <div
      onClick={moveToTop}
      className={cls(
        showTopBtn ? "opacity-100 visible" : "opacity-0 invisible",
        "fixed bottom-7 right-4 lg:right-10 p-2 z-20 cursor-pointer hover:scale-90 transition-all duration-500 rounded-full bg-[#2c82f2]"
      )}
    >
      <HiChevronUp className="text-2xl text-white" />
    </div>
  );
}

 

 

 

 

특정 영역 안에서 상단 이동

다른 프로젝트에서 탭이 있는 모달 창을 만들던 중 스크롤이 생기는 모달이어서 탭 클릭 시 자동으로 위에서 부터 다시 시작하게 만들어야 했다.

DOM요소에 접근하기 위해 사용되는 React Hook 인 useRef 를 활용하여 해당 영역을 참조한다.

아래 코드에서는 참조된 영역의 scrollTop이 0이 아니면 수직 스크롤 바의 위치를 0으로 변경한다.

import { useState, useEffect, useRef } from "react";

export default function Ex() {
	const [modalTab, setModalTab] = useState(1);
  const modalInfoModal = useRef<HTMLDivElement>(null);

  const selectModalTabHandler = (order: number) => {
    setModalTab(order);

    if (modalInfoModal.current.scrollTop !== 0) {  // 상단으로 이동
      modalInfoModal.current.scrollTop = 0;
    }
  };

	return (
		<Modal>
			<ul>
         <li
           onClick={() => selectModalTabHandler(1)}
           className={modalTab === 1 ? "on" : ""}
         >
           탭 1
         </li>
         <li
           onClick={() => selectModalTabHandler(2)}
           className={modalTab === 2 ? "on" : ""}
          >
          탭 2
         </li>
      </ul>
			<div
        ref={modalInfoModal}
       >
				{modalTab === 1 ? (      
            <div className="topRank"> 탭 1 content</div>
        ) : (
            <div className="topRank"> 탭 2 content</div>
        )}
			</div>
		<Modal/>
	)
}