[framer-motion] 애니메이션 예제 1 - 부모 자식이 있는 애니메이션!

Pakxe·2022년 8월 1일
4

framer-motion

목록 보기
1/1
post-thumbnail

framer-motion을 이용한 애니메이션이 처음이니까 기본적인 준비를 해봅시다.

라이브러리 설치

npm i framer-motion


import

import { motion } from "framer-motion"; 

styled-component를 motion.div로 선언하기

애니메이션을 적용할 컴포넌트를 만들때는 원래 만들던 방식인

const Component = styled.div

가 아니라

const Component = styled(motion.div)

와 같이 생성해야합니다. (다른 컴포넌트를 상속받은 새로운 컴포넌트를 만드는 방식임)

그래야 애니메이션 적용이 가능하게 됩니다. 왜냐면 이 motion안에는 html의 모든 태그가 있고 각 태그에 애니메이션을 적용할 props가 선언되어 있기 때문에 우리는 쉽게 키워드 만으로 사용할 수 있는 것입니다.


예제 1

큰 박스가 먼저 빙글 돌면서 나타나고, 뒤이어 이 안에 작은 원 4개가 차례대로 떠오르는 애니메이션을 만들어 봅시다.

완성되면 이런 모습이어야 합니다.

일단 박스 컴포넌트를 만들어 기본적인 설정을 해줍니다.

const Box = styled(motion.div)`
  display: flex;
  justify-content: space-around;
  align-items: center;
  flex-wrap: wrap;
  height: 200px;
  width: 200px;
  border-radius: 30px;
  background-color: rgba(255, 255, 255, 0.4);
  box-shadow: 0 2px 5px 3px rgba(0, 0, 0, 0.1);
`;

그리고 variants를 이용해 코드가독성이 높은 애니메이션을 작성해보겠습니다.


variants 란?

애니메이션 설정들을 변수로 만들어서 태그 안에 길게 애니메이션을 작성할 필요 없이 변수호출 만으로도 애니메이션을 사용할 수 있도록 한 것

위 사진과 같이 props로 만든 애니메이션 변수를 전달해주면 됩니다.
태그 안에 길게 delay는 몇초고.. 회전은 얼만큼하고.. 와 같은 속성들을 적어줄 필요가 없기 때문에 보기가 쉬워집니다.

variant="애니메이션이름"

variants에 집어넣을 애니메이션 변수를 만들자

variants 를 사용하려면 일단 애니메이션들을 적을 변수를 만들어야겠습니다.
아래와 같은 형식으로 작성합니다.

const BoxAnimation = {
  start: {}, 
  end: {},
};

오브젝트 형식입니다.

만든 variants를 적용하려면,

이런식으로 사용하면 됩니다.


만든 애니메이션 변수에 처음모습과 종료모습을 작성하자

바로 위 코드를 보면 BoxAnimation변수에 start, end라는 키값이 있습니다. 이건 다른 키워드로 대체해도 괜찮습니다.
왜냐면 우리가 motion을 상속받은 객체를 만들 때 이 motion이 원하는 props가 여러개 있는데 이중 initial, animate에 우리가 명명한 시작모습, 종료모습을 할당하면 되기 때문입니다.

위 사진과 같이 전달해주기 때문에 다른이름을 사용해도 됩니다.
저는 앞으로 계속 start, end로 진행하겠습니다.

일단 처음에는 투명하고 작았다가, 커지면서 색이 채워지는 느낌으로 애니메이션을 준다고 한다면

const BoxAnimation = {
  start: { scale: 0, opacity: 0}, 
  end: { scale: 1, opacity: 1},
};

위와 같이 작성해주면 됩니다.

scale: 컴포넌트의 크기를 말하고 0~1까지의 범위를 갖습니다
opacity: 컴포넌트의 투명도를 말하고 0~1까지의 범위를 갖습니다. 이 속성을 사용할 때 주의점은 자손 컴포넌트까지 모두 이 opacity속성이 적용됩니다. 따라서 부모만 투명하게 하고, 자식은 불투명으로 하고 싶다면 다른 속성(rgba 등)을 사용해야 합니다.

그런데 이 애니메이션은 처음, 종료 모습만 지정해줬지 어떤 방식으로, 몇 초 동안 진행되는지 정해주지 않았습니다.
이것에 대한 설정은 transition props를 이용하면되는데, 공식 문서에 있는 transition 의 뜻을 보면
A transition defines how values animate from one state to another. 라고 적혀 있습니다.

따라서 어떻게 진행될지에 대한 속성은 이 transition 안에 적어주면 된다는 것을 알고 넘어가면 됩니다.


transition 을 작성하자

이 transition이라는 속성은 end안에 작성하면 됩니다. end라는 오브젝트 안에 transition이라는 키 값이 있고 이 키값 안에 속성들이 있는 구조입니다.

일단 이 애니메이션이 3초 동안 진행되게 설정해봅시다.

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 }, 
  end: {
    scale: 1,
    opacity: 1,
    transition: {
      duration: 3,
    },
  }, 
};

duration: 애니메이션 길이를 설정한다. 초 단위

duration이라는 값을 추가해 3초 동안 진행되도록 만들었습니다.

지금까지의 모습은 이렇게 됩니다.


띠용하고 나타나는 느낌을 줘보자

그런데 모션이 너무 허전한 느낌이 듭니다. 뭔가 띠용 하면서 갑자기 나오는 느낌을 주고 싶어요.

그러므로 아래의 속성을 더 추가해보겠습니다.

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 }, 
  end: {
    scale: 1,
    opacity: 1,
    transition: {
      duration: 3,
      type: "spring"
      stiffness: 110,
    },
  }, 
};

type:"spring" -> 스프링처럼 느려졌다가 빨라졌다가 다시 느려지는 느낌을 줍니다. 탄성을 갖는 것처럼 반동이 생깁니다.
stiffness: 강성. 변형에 저항하는 성질로, 시작시 살짝 힘에 저항하다가 갑자기 튀어오르는 느낌을 줍니다. 한 사람이 다른 사람을 당기면 다른사람이 최대한 버티다가 한계점을 넘으면 갑자기 앞으로 쏠리며 튀어나가는 것을 상상하시면 될 것 같습니다. spring과 같이 사용하면 더 탱탱한 효과를 만들 수 있습니다.


spring만 적용하면 위 모습이고,


stiffness를 함께 적용한 모습입니다.


빙글빙글 돌려보자

처음 모습보단 낫지만, 돌면서 등장하면 더 멋있을 것 같습니다.
이 설정은 transition안이 아닌 밖에 작성해줍니다. opacity와 같은 위치에 작성하면 됩니다.

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 }, 
  end: {
    scale: 1,
    opacity: 1,
    rotateZ: 360,
    transition: {
      duration: 3,
      type: "spring"
      stiffness: 110,
    },
  }, 
};

rotateZ: 평면 상에서 돌게합니다. 각도를 int형으로 입력합니다. rotateZ가 있다면 rotateX, rotateY도 있을텐데, 직접 입력하면서 알아보시면 될 것 같습니다.

결과


이제 자식 컴포넌트를 추가해보자.

우리가 원래 원하던 모습은 지금까지 만든 박스가 나타난 후에 4개의 작은 원이 순차적으로 떠오르는 모습입니다.

따라서 자식 또한 마찬가지로 motion.div를 상속받은 컴포넌트로 만들고, 애니메이션 변수도 만들어 줍니다.

const Inner = styled(motion.div)`
  height: 70px;
  width: 70px;
  border-radius: 50%;
  background-color: white;
  box-shadow: 0 2px 5px 3px rgba(0, 0, 0, 0.1);
`;

const InnerAnimation = {
  start: { opacity: 0, y: 10 },
  end: { opacity: 1, y: 0 },
};

*x, y 속성은 이동방향과 이동량(상대위치)을 설정해주면 됩니다. x의 양의방향은 오른쪽이고, y의 양의방향은 아래쪽입니다. 음수로 지정해주면 반대로 이동하겠죠?

시작위치는 y: 10이고, 종료위치는 y: 0 을 적었습니다. 이 뜻은 10 -> 0으로 이동하는 것이므로 y의 음의방향입니다. 따라서 Inner 컴포넌트는 위쪽으로 올라올 것 입니다.
(방향이 이해가 안간다면 x도 넣어보고 음수도 넣어보며 익히면 바로 이해할 수 있습니다.)


자식 컴포넌트에서 variants 설정을 해주자

바로 위에서 우린 자식컴포넌트에 적용할 애니메이션 변수(InnerAnimation)를 생성했습니다. 이걸 적용하기 위해선 부모인 Box컴포넌트에 해줬던 것처럼 variants="애니메이션이름" 을 해주면 됩니다. 이때 부모 컴포넌트 호출시 적어줬던 initial, animate는 자식에게 상속 되므로 자식에게는 적지 않아도 됩니다.

<Box initial="start" animate="end" variants={BoxAnimation}>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
      </Box>

자식이 나중에 나왔으면 좋겠다.. 그리고 순차적으로 나오기까지

자식과 부모를 다 설정해주고 실행해보면

이렇게 나오게 됩니다.

처음 원하던 모습이 아닙니다.. 자식이 부모와 함께 나오면 안될 것 같습니다. 부모와 자식간의 딜레이를 설정해봅시다.

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 }, 
  end: {
    scale: 1,
    opacity: 1,
    rotateZ: 360,
    transition: {
      duration: 3,
      type: "spring"
      stiffness: 110,
      delayChildren: 2,
    },
  }, 
};

delayChildren: 부모와 자식간의 간격을 지정합니다. 초 단위 입니다.

자식이 부모인 Box가 나온뒤에 뜨기는 하는데, 순차적으로 뜨지 않습니다. 그러면 자식과 자식간의 딜레이를 설정해봅시다.

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 }, 
  end: {
    scale: 1,
    opacity: 1,
    rotateZ: 360,
    transition: {
      duration: 3,
      type: "spring"
      stiffness: 110,
      delayChildren: 2,
      staggerChildren: 0.5,
    },
  }, 
};

성공~! 우리가 처음 원하던 애니메이션과 동일해졌습니다!

예제 1은 여기서 끝입니다.
아래는 전체 코드입니다.

import { motion } from "framer-motion";
import styled from "styled-components";
import React from "react";

const BoxAnimation = {
  start: { scale: 0, opacity: 0.5 },
  end: {
    scale: 1,
    opacity: 1,
    rotateZ: 360,
    transition: {
      duration: 3.5,
      type: "spring",
      stiffness: 110,
      delayChildren: 2,
      staggerChildren: 0.5,
    },
  },
};

const InnerAnimation = {
  start: { opacity: 0, y: 10 },
  end: { opacity: 1, y: 0 },
};
const Inner = styled(motion.div)`
  height: 70px;
  width: 70px;
  border-radius: 50%;
  background-color: white;
  box-shadow: 0 2px 5px 3px rgba(0, 0, 0, 0.1);
`;

const Background = styled.div`
  background: linear-gradient(45deg, #fbc2eb, #a6c1ee);
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Box = styled(motion.div)`
  display: flex;
  justify-content: space-around;
  align-items: center;
  flex-wrap: wrap;
  height: 200px;
  width: 200px;
  border-radius: 30px;
  background-color: rgba(255, 255, 255, 0.4);
  box-shadow: 0 2px 5px 3px rgba(0, 0, 0, 0.1);
`;
function App() {
  return (
    <Background>
      <Box initial="start" animate="end" variants={BoxAnimation}>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
        <Inner variants={InnerAnimation}></Inner>
      </Box>
    </Background>
  );
}

export default App;
profile
내가 꿈을 이루면 나는 또 누군가의 꿈이 된다.

2개의 댓글

comment-user-thumbnail
2022년 8월 2일

팩세님 블로그 잘봤어요~ 열공하세요 빠세!

답글 달기
comment-user-thumbnail
2022년 8월 2일

framer motion 관련해서 잘 참고하겠습니다 :)

답글 달기