framer-motion을 이용한 애니메이션이 처음이니까 기본적인 준비를 해봅시다.
npm i framer-motion
import { motion } from "framer-motion";
애니메이션을 적용할 컴포넌트를 만들때는 원래 만들던 방식인
const Component = styled.div
가 아니라
const Component = styled(motion.div)
와 같이 생성해야합니다. (다른 컴포넌트를 상속받은 새로운 컴포넌트를 만드는 방식임)
그래야 애니메이션 적용이 가능하게 됩니다. 왜냐면 이 motion안에는 html의 모든 태그가 있고 각 태그에 애니메이션을 적용할 props가 선언되어 있기 때문에 우리는 쉽게 키워드 만으로 사용할 수 있는 것입니다.
큰 박스가 먼저 빙글 돌면서 나타나고, 뒤이어 이 안에 작은 원 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를 이용해 코드가독성이 높은 애니메이션을 작성해보겠습니다.
애니메이션 설정들을 변수로 만들어서 태그 안에 길게 애니메이션을 작성할 필요 없이 변수호출 만으로도 애니메이션을 사용할 수 있도록 한 것
위 사진과 같이 props로 만든 애니메이션 변수를 전달해주면 됩니다.
태그 안에 길게 delay는 몇초고.. 회전은 얼만큼하고.. 와 같은 속성들을 적어줄 필요가 없기 때문에 보기가 쉬워집니다.
variant="애니메이션이름"
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이라는 속성은 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도 넣어보고 음수도 넣어보며 익히면 바로 이해할 수 있습니다.)
바로 위에서 우린 자식컴포넌트에 적용할 애니메이션 변수(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;
팩세님 블로그 잘봤어요~ 열공하세요 빠세!