framer-motion을 이용한 스크롤 감지 애니메이션 (React-Typescript)

Devinix·2024년 2월 27일
0
post-thumbnail

useSlideFilterOption 훅

import { AnimationControls, useAnimation } from "framer-motion";
import { MutableRefObject, useEffect, useRef, useState } from "react";

interface IReturn {
  homeContentsRef: MutableRefObject<HTMLDivElement | null>;
  controls: AnimationControls;
}

function useSlideFilterOption(): IReturn {
  const [scrollY, setScrollY] = useState<number>(0); // 현재 스크롤 위치
  const [lastScrollY, setLastScrollY] = useState<number>(0); // 마지막 스크롤 위치
  const homeContentsRef = useRef<HTMLDivElement | null>(null); // 스크롤 영역 컨텐츠
  const [scrollDirection, setScrollDirection] = useState<"up" | "down" | null>(
    null
  ); // 스크롤 방향을 저장하기 위한 상태 변수
  
  // Framer Motion 애니메이션 컨트롤러
  const controls = useAnimation();

  // 스크롤 이벤트를 처리하기 위한 useEffect
  useEffect(() => {
    const handleScroll = () => {
      if (homeContentsRef.current) {
        const homeContentsScrollY = homeContentsRef.current.scrollTop;
        setScrollY(homeContentsScrollY);

        // 스크롤 방향 판단 로직
        if (scrollY > lastScrollY && scrollY >= 60) {
          setScrollDirection("down");
        } else if (scrollY < lastScrollY && scrollY >= 60) {
          setScrollDirection("up");
        } else {
          setScrollDirection(null);
        }

        setLastScrollY(scrollY);
      }
    };

    const element = homeContentsRef.current;
    element?.addEventListener("scroll", handleScroll);

    // 컴포넌트 언마운트 시 이벤트 리스너 제거
    return () => {
      element?.removeEventListener("scroll", handleScroll);
    };
  }, [scrollY, lastScrollY]); // 의존성 배열에 scrollY, lastScrollY 포함

  // 스크롤 방향에 따라 애니메이션 실행
  useEffect(() => {
    if (scrollDirection === "up") {
      controls.start({
        y: 0,
        opacity: 1,
        transition: { type: "tween", duration: 0.3 },
      });
    } else if (scrollDirection === "down") {
      controls.start({
        y: -60,
        opacity: 0,
        transition: { type: "tween", duration: 0.3 },
      });
    }
  }, [scrollDirection, controls]); // 의존성 배열에 scrollDirection, controls 포함

  return { homeContentsRef, controls };
}

export default useSlideFilterOption;

HomeMain 컴포넌트

function HomeMain(): JSX.Element {
  const { homeContentsRef, controls } = useSlideFilterOption();

  return (
    <main className={styles.container}>
      <section ref={homeContentsRef} className={styles.homeContents}>
        <motion.section
          animate={controls}
          className={styles.filterOptionContainer}
        >
          <p className={styles.filterOption}>필터링 옵션</p>
        </motion.section>
        {[...Array(12)].map((_, i) => (
          <HomeContent key={i} />
        ))}
      </section>
    </main>
  );
}

수정!!!

위 방법은 불필요한 리렌더링을 너무 많이 발생시켜서 코드를 조금 수정 했습니다.

https://velog.io/@dpldpl/useState-%EB%8C%80%EC%8B%A0-useRef%EB%A1%9C-React-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0

profile
프론트엔드 개발

0개의 댓글