Project/탑개미자원

[Framer Motion] 컴포넌트가 사라질때(언마운트시) 애니메이션 효과를 적용할 수 있을까?

2워노 2023. 11. 27. 01:59

📝 배경

  • 탑개미자원 웹 제작 프로젝트를 진행하며, 스크롤을 일정 범위 이상으로 내릴 시 페이지 최상단으로 이동하는 TopButton이라는 컴포넌트가 조건부렌더링 되도록 구현중이었습니다.
  • 페이지 최상단으로 이동하는 기능을 구현하는것은 쉬우나, 해당 컴포넌트가 마운트, 언마운트 될 시 애니메이션 효과를 부여하고 싶은 상황이었습니다.
  • 해당 프로젝트에의 각종 애니메이션 효과는 Framer Motion이라는 라이브러리를 사용중이었습니다.

🔍️ 문제 상황

  • 애니메이션 효과는 그냥 라이브러리를 통해서 구현하면 되는거 아닌가? 라고 생각할수도 있습니다.
  • 물론 React나 next.js 에서 컴포넌트는 조건부 렌더링을 통해 쉽게 추가되거나 제거할 수 있죠.
  • 하지만, 일반적으로 컴포넌트가 사라질 때 애니메이션을 적용하는 것은 어렵습니다.
  • 왜냐하면 컴포넌트가 언마운트되면 즉시 DOM에서 제거되기 때문입니다. 이 때문에 해당 컴포넌트에 연결된 모든 애니메이션도 즉시 중단되어 버리게 됩니다..
  • 그럼 어떤식으로 구현을 해야할까요? 아래 예시 코드를 살펴보겠습니다.(react 코드)
import { useState, useEffect } from 'react';
import './MyComponent.css'; // CSS 애니메이션을 정의한 CSS 파일

const MyComponent = () => {
  const [visible, setVisible] = useState(true);

  useEffect(() => {
    return () => {
      setVisible(false);
      setTimeout(() => {
        // 여기서 컴포넌트를 언마운트합니다.
      }, 1000); // CSS 애니메이션의 지속 시간과 일치하게 설정
    };
  }, []);

  return (
    <div className={`my-component ${visible ? 'enter' : 'exit'}`}>
      {/* 컴포넌트 내용 */}
    </div>
  );
};

// my-component 클래스에는 기본 애니메이션 스타일이, enter 클래스에는 컴포넌트가 마운트될 때 적용할 애니메이션 스타일이, exit 클래스에는 컴포넌트가 언마운트될 때 적용할 애니메이션 스타일이 CSS에서 정의되어 있어야 합니다
  • 위와 같이, useState와 useEffect 그리고 useEffect의 콜백함수 안에 setTimeout 함수를 통해 css 애니메이션의 지속시간과 일치하게 설정해줘야 합니다. 다소 번거로운 작업이네요.
  • 또한, 위 방법은 컴포넌트가 실제로 언마운트되지 않고, 상태 변경에 의해 애니메이션만 적용되는 것이므로 주의해야 합니다.

 

🔨 Framer Motion 라이브러리 에서는?

  - AnimatePresence

  • Framer Motion 라이브러리에서는 AnimatePresence라는 컴포넌트를 제공하고 있습니다.
  • AnimatePresence는 위의 예시와 같은 한계? 불편한점을 극복하기 위해 설계되었습니다.
  • AnimatePresence  컴포넌트로 감싸진 자식 컴포넌트들은 exit 속성을 통해 언마운트 애니메이션을 정의할 수 있습니다. 
  • 이 exit 애니메이션은 컴포넌트가 React 트리에서 제거되기 전에 실행되며, 이렇게 하면 컴포넌트가 사라질 때도 애니메이션을 적용할 수 있습니다.
  • 따라서 AnimatePresence는 컴포넌트가 마운트되거나 언마운트될 때 애니메이션을 적용하는 데 매우 유용하다고 할 수 있습니다.

 

  - 실제 코드 적용(next.js 14버전)

"use client";

import { useEffect, useState } from "react";
import { BiUpArrow } from "react-icons/bi";
import { motion, AnimatePresence } from "framer-motion";

const TopButton = () => {
  const [isScrolling, setIsscrolling] = useState<boolean>(false);

  const hoverMotion = {
    transition: {
      type: "spring",
      stiffness: 500,
      damping: 15,
    },
    whileHover: { scale: 1.2 },
  };

  useEffect(() => {
    const handleScroll = () => {
      if (window.scrollY > 100) {
        setIsscrolling(true);
      } else {
        setIsscrolling(false);
      }
    };
    window.addEventListener("scroll", handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  // 스크롤 최상단으로 이동하는 핸들러함수
  const handleButtonClick = () => {
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  return (
    <AnimatePresence>
      {isScrolling && (
        <motion.div
          initial={{ y: 100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: 100, opacity: 0 }}
          {...hoverMotion}
          className="fixed bottom-7 right-7 z-50 bg-red-500 rounded-full p-3 shadow-2xl"
          onClick={handleButtonClick}
        >
          <BiUpArrow color="white" size={"23px"} />
        </motion.div>
      )}
    </AnimatePresence>
  );
};

export default TopButton;
  • AnimatePresence 컴포넌트를 이용하여 간단하게 마운트, 언마운트 시 애니메이션 효과를 적용 할 수 있습니다!

마운트, 언마운트시 애니메이션 적용.gif