Framer-motion으로 애니메이션 다루기

yesung·2024년 1월 29일
1
post-thumbnail

<< 이전 탑 스크롤 구현 작업

소개 페이지 최종 시안이 나와서 작업을 바로 들어갔다. 중간에 와이어 프레임대로 짜 봤지만 역시 전체를 다 바꿔야 했고 css keyframe으로 하기엔 리소스가 많이 들어서 framer-motion 라이브러리를 사용하기로 했다.

npm install framer-motion
yarn add framer-motion

sticky를 이용한 motion

/**
 * 상수 데이터
 * @description 키오스크 스크롤에 따른 애니메이션 효과를 위한 상수
 */
export const SCROLL_THRESHOLDS = [1200, 1900, 2600];
...

// 사용된 컴포넌트
const KioskVideo = () => {
  const [index, setIndex] = useState(0);
  const [ref] = useInView({ triggerOnce: true, threshold: 0.8 });

  useEffect(() => {
    const handleScroll = throttle(() => {
      for (let i = 0; i < SCROLL_THRESHOLDS.length; i++) {
        if (window.scrollY > SCROLL_THRESHOLDS[i]) setIndex(i);
      }
    }, 200);

    window.addEventListener('scroll', handleScroll);

    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <section className={styles.kioskBox}>
      <div className={styles.stickyBox}>
        <TransitionBox index={index} />
        <div className={styles.titleBox} ref={ref}>
          <TransitionText className={styles.t1} text={MAIN_TITLES[index]} index={index} />
          <div className={styles.subTitle}>
            <TransitionText text={FIRST_CAPTIONS[index]} index={index} />
            <TransitionText text={SECOND_CAPTIONS[index]} index={index} />
          </div>
        </div>
      </div>
    </section>
  );
};

마우스 스크롤 y축은 콘솔에 찍어보며 계산했고 동적으로 텍스트를 변경해주기 위해 for loop으로 스크롤을 순회하면서 인덱싱 처리를 했다. (텍스트와 이미지 배열 순서를 맞춤)

그리고 기존에 사용하던 react-intersection-observeruseInView 를 활용하여 화면에 보여질 때, 이벤트가 발동되게 걸어놨다. (메모리 누수 방지를 위해 이벤트 제거는 필수)

throttle은 스크롤을 할 때 마다, 이벤트가 일어나서 스크롤 함수에 적용 시켜서 일정 시간 간격으로 제한을뒀다.

예를 들면, 이벤트가 일어날 때 마다 실행이 되면 일정 시간 동안 이전에 실행한 함수가 있으면 그 함수의 결과를 반환하고 그렇지 않으면 새로운 함수를 실행하는 기법이다.
(2초 마다 한 번씩만 실행되고 그 사이에는 이전에 실행한 함수 결과를 반환. 결국 스크롤이 빈번하게 일어나도 실행 횟수를 제한할 수 있음)

const TransitionBox = (props: Props) => {
  const { index } = props;

  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 1 }}>
      {IPHONES.map((iphone, i) => (
        <motion.div
          className={styles.iphoneBox}
          key={iphone.id}
          initial={{ opacity: i === index ? 1 : 0 }}
          animate={{ opacity: i === index ? 1 : 0 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 1 }}
        >
          {i === index && <Image src={iphone.src} width={370} height={1000} alt={iphone.alt} priority />}
        </motion.div>
      ))}
    </motion.div>
  );
};
const TransitionText = (props: Props) => {
  const { text, index, className } = props;

  return (
    <motion.p
      className={className}
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      key={index}
      transition={{ duration: 0.5 }}
    >
      {text}
    </motion.p>
  );
};

실직적으로 애니메이션은 TransitionBox TransitionText 컴포넌트에서 이루어진다.

  • inital로 시작 상태를 알려주고 인덱싱에 맞게 opacity 값을 부여
  • animate 전환 효과 시 부여할 옵션
  • exitd은 종료될 시점
  • transition 전환 효과 / duration 지속 시간
profile
Frontend Developer

0개의 댓글