framer-motion.til

Universe·2023년 4월 2일
1

framer-motion

프로젝트에서 추가적인 구현사항으로 동적인 애니메이션을 추가하여
모션 디자인이 포함된 웹 사이트를 구현해보고자 한다.

고민했던 라이브러리는 framer-motion 과 gsap.
빠른 시간에 기술을 녹여내야 하는 실전프로젝트 특성상,
러닝커브가 높은 기술은 아무래도 사용하기 망설여졌고 (나만 사용할 게 아니기 때문에)
우리 프로젝트의 기술적인 모토가 '쉬운 사용성', '코드 단순화', '효율적인'
이 세가지 였기 때문에 더 높은 자유도를 가졌지만 코드량이 많고
특정 기능은 유료인 gsap보다는 framer-motion 이 적합하다고 판단했다.

framer-motion 은 일단 공식문서가 굉장히 친절하고
React 환경에 친숙하게 만들어 놓았기 때문에 러닝커브가 낮다는 게 가장 큰 장점이다.
기술적으로도 우수한데, GPU 가속 애니메이션과 최적화된 렌더링을 제공해서 성능에 부담이 없으며,
60 프레임 애니메이션을 제공해서 UX 적인 측면에서도 아주 우수하다.

라이브러리 번들링 측면에서도 모든 모듈이 ES6 모듈 시스템을 사용하고 있기 때문에
CRA 로 빌드되어 웹팩을 사용하는 우리의 프로젝트에서도 따로 트리쉐이킹을 하지 않아도
효율적으로 번들링을 해줄 것이다. gsap의 이전 버전이 ES6 모듈을 지원하지 않는다는 점과 대조적.

여러가지 사용법

framer-motion을 사용하고 싶다면 반드시 motion 객체에서 elements를 꺼내와야 한다.
또한 styled-components 와 호환되기 때문에 걱정이 없다.

import {motion} from 'framer-morion'

<motion.div/>
<motion.header/>
<StyledWrapper/>

const StyledWrapper = styled(motion.div)`
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
  `,

처음에 이것 저것 만져보다가 옵션이 너무 길어지는 것 같아서

const framerOption = {
    transition: { type: "spring", delay: 1, bounce: 0.5 },
    initial: { scale: 0 },
    animate: { scale: 1, rotateZ: 360 },
  };


<StyledMotion.Box {...framerOption} />

이런식으로 리펙토링해서 사용하나 ? 하고 조금 바꿔봤었는데
이 훌륭한 라이브러리는 자체적으로 그런 기능도 제공한다.

const myVars = {
	start: {scale:0}
	end: {scale:1, rotateZ:360, transition:{type: "spring", delay: 1, bounce: 0.5}}
}

<StyledMotion.Box variants={myVars} initial="start" animate="end" />

자체적으로 variants 라는 옵션을 제공하고 initial과 animate에 제공할 속성을
객체의 key 값으로 넣을 수 있다.
Variants 는 이 뿐만이 아니라 자식요소도 컨트롤 할 수 있다.

const boxMotion = {
    start: { opacity: 0, scale: 0.5 },
    end: {
      scale: 1,
      opacity: 1,
      transition: {
        type: "spring",
        duration: 0.5,
        bounce: 0.5,
        delayChildren: 0.2,
				// 부모 요소의 애니메이션이 끝난 후 딜레이
        staggerChildren: 0.2,
				// 자식요소 간의 딜레이 
      },
    },
  };

이러한 요소 말고도 while~ 로 시작하는 이벤트를 감지하는 애니메이션 이라던지,
drag 할 수 있는 elements 를 만드는 등의 작업을 기대해볼 수 있다.
이러한 작업은 공식문서를 확인하면 되겠다.

framer-motion 을 사용해 모달에 애니메이션 넣기

항상 연습은 typescript 로 진행하기 때문에
프로젝트의 javascript 를 변환하는데 조금 시간이 걸렸다.
하지만 구현한 모달 컴포넌트에서 크게 달라진 부분은 없다.

import { motion, AnimatePresence } from "framer-motion";

... 

const containerMotion = {
    start: { scale: 0, opacity: 0 },
    end: { scale: 1, opacity: 1, transition: { duration: 0.3 } },
    exit: { scale: 0, opacity: 0, transition: { duration: 0.3 } },
  };

return createPortal(
    <AnimatePresence>
      {modalState.isOpen && (
        <StyledModal.Overlay
          onClick={handleOverlayClick}
          canCloseOnOverlayClick={canCloseOnOverlayClick}
        >
          <StyledModal.Container
            onClick={(e) => e.stopPropagation()}
            padding={padding}
            width={width}
            height={height}
            variants={containerMotion}
            initial="start"
            animate="end"
            exit="exit"
            key="modal"
          >
            {isCloseButton && (
              <StyledModal.CloseButton onClick={closeModal}>
                &times;
              </StyledModal.CloseButton>
            )}
            <StyledModal.Title>{modalState.title}</StyledModal.Title>
            <StyledModal.Contents>{modalState.contents}</StyledModal.Contents>
            {modalState.callback && (
              <StyledModal.Footer>
                <button onClick={closeModal}>Cancel</button>
                <button onClick={modalState.callback}>Ok</button>
                {/* 모달 디자인이 나오면 버튼 디자인, 문구 변경할 것 */}
              </StyledModal.Footer>
            )}
          </StyledModal.Container>
        </StyledModal.Overlay>
      )}
    </AnimatePresence>,
    document.getElementById("modal-root") as HTMLElement
  );

추가적으로 AnimatePresence 라는 컴포넌트가 필요한데,
모달이 열릴 때 뿐만 아니라 닫힐 때도 애니메이션을 주기 위해서는
컴포넌트가 언마운트 되기 전에 애니메이션의 작업을 실행한 뒤에 언마운트 될 수 있도록
수명주기를 바꾸는 역할.

AnimatePresence로 언마운트를 연기할 컴포넌트를 감싸고 exit 옵션과 key를 해당 element 에 할당해주는 식으로 구현할 수 있다.

순서가 굉장히 중요한데
portalAnimatePresencecomponrnts
이 순서를 지켜야 한다.

AnimatePresence 컴포넌트의 작동원리는 어떻게보면 ‘언마운트를 약간 늦춰
그동안 애니메이션을 실행하기’ 이기 때문에 portal 이라는 root 안에
AnimatePresence 를 넣고 그 안에서 조건문에 의해 컴포넌트가 마운트, 혹은 언마운트 되더라도
AnimatePresence 의 영향을 받을 수 있도록 해야만 한다.

이 순서를 헷갈려서 두 시간을 헤맸기 때문에 다시 헷갈릴 일은 없을 것 같다.
framer-motion으로 toast 구현하기
해당 링크를 참고했다.




마무리

변명 할 필요 없이 최근에 자꾸 늘어지게 된다.
글로 남기고 싶은 것도 많은데 자꾸 미루게 되어 슬프다.
봄이라 나른해서 그런가 ?
항상 일요일 저녁에는 여지없이
다음주에는 꼭 갓생을 살거야 하고 다짐해본다.

profile
Always, we are friend 🧡

0개의 댓글