# **Framer** Motion
- **React JS**에서 애니메이션을 쉽고 강력하게 만들어주는 라이브러리이다.
- 24년 11월 12일부로 **framer-motion**이 **framer**로부터 독립해 **motion**이라는 이름이 되었다.
```shell
npm install framer-motion
```
## Motion Tag
- 어떠한 태그에 Framer Motion을 적용시키기 위해서는 태그 이름 앞에 `motion` 을 붙여야한다.
```jsx
function Animation() {
return (
<Wrapper>
<Box />
<motion.div></motion.div> // div가 아닌 motion.div
<motion.p></motion.p> // p가 아닌 motion.p
<motion.img/> // img가 아닌 motion.img
</Wrapper>
);
}
```
## Motion Props
- Motion은 애니메이션을 넣고 싶은 Tag에 Props를 넣기만 하면 손 쉽게 애니메이션이 적용된다.
### Initial
- 초기에 원하는 상태를 정의한다.
- Nomal CSS로 정의하면 된다.
### Animation
- 원하는 동작을 정의한다.
- **scale** : 크기
- **rotateX, Y, Z** : X, Y, Z축으로 특정 각도만큼 회전
### Transition
- **type** : 애니메이션의 여러 타입을 정의(ex : spring[default], tween ..)
- Spring Type의 여러 설정을 조정할 수도 있다. (ex : stiffness, damping)
- **duration** : 완료될 때까지 걸리는 시간
- **delay :** 랜더링 된 이후 ****animation을 시작하는 시간
- **delayChildren** : 자식들의 delay 시간
- **staggerChildren** : 각 자식들의 delay 간격 시간
### drag
- 해당 컴포넌트를 드래그 할 수 있도록 정의한다.
- 기본값은 상하좌우 움직일 수 있다.
- **drag = “x”**로 설정 시 x축 방향으로만 움직일 수 있다.
- **drag = “y”**로 설정 시 y축 방향으로만 움직일 수 있다.
### dragSnapToOrigin
- 해당 컴포넌트 드래그 이후 원래 있던 위치로 되돌아가도록 한다.
### dragElastic
- 0~1 사이의 값을 가지며 드래그 시 탄성 정도를 정의할 수 있다.
### dragConstraints
- 드래그 가능 구역을 정의 할 수 있다.
- top, bottom, left, right를 통해 지정된 구역을 정의 할 수 있다
- `useRef` 를 활용해 특정 컴포넌트 자체를 제약 구역으로 설정 할 수도 있다.
### whileHover
- **Hover** 하는 동안 실행될 **animation**을 정의한다.
### whileTap
- **Click** 하는 동안 실행될 **animation**을 정의한다.
### whileDrag
- **Drag** 하는 동안 실행될 **animation**을 정의한다.
### Layout
- 특정 컴포넌트의 위치가 바뀔 때 자동으로 애니메이션 효과를 넣어준다.
```jsx
<Box style={{justifyContent: visible ? "center" : "flex-start", alignItems: visible ? "center" : "flex-start"}}>
<Circle layout/>
</Box>
```
### LayoutID
- 여러 컴포넌트를 하나로 연결해줘서 **각 컴포넌트들 간의 이동(변화)**을 애니메이션으로 표현해준다
```jsx
function Animation() {
const [visible, setVisible] = useState(true);
const toggleVisible = () => setVisible((prev) => !prev);
return (
<Wrapper onClick={toggleVisible}>
<Box >
{!visible && <Circle layoutId="circle" style={{borderRadius: 50}}/>}
</Box>
<Box >
{visible && <Circle layoutId="circle" style={{borderRadius: 0}}/>}
</Box>
</Wrapper>
);
}
```
## Motion Value
- **useMotionValue**를 통해 해당 컴포넌트의 움직임 등의 값을 얻고 수정 할 수 있다.
- **Motion Value**의 값이 바뀐다고 해서 React는 다시 **랜더링이 되지 않는다.**
## Animation Presence
- 컴포넌트가 사라질 때 생기는 애니메이션을 관리한다.
- 관리할 컴포넌트를 Animation Presence Tag 안에 생성하여야 한다.
```jsx
const boxVariants = {
start : {
opacity : 0,
scale : 0,
},
visible : {
opacity : 1,
scale : 1,
rotateZ : 360,
},
leaving : {
opacity : 0,
y:20,
}
}
function Animation() {
const [visible, setVisible] = useState(true);
const toggleVisible = () => setVisible((prev) => !prev);
return (
<Wrapper>
<button onClick={toggleVisible}>Toggle</button>
<AnimatePresence>
{visible && <Box variants={boxVariants} initial="start" animate="visible" exit="leaving" />}
</AnimatePresence>
</Wrapper>
);
}
```
## Demo
### Spring
```jsx
const Box = styled(motion.div)`
width: 200px;
height: 200px;
background-color: white;
border-radius: 15px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
<Box initial={{ scale: 0 }} animate={{ scale: 1, rotateZ: 360 }} transition={{ type: "spring", bounce: 0.5 }}/>
```
### Variants
- **Variants**를 사용하면 props를 더 보기 깔끔하게 만들 수 있다.
- **Variants**로 사용할 변수를 선언 한 뒤, 변수 값(inital, animate)를 선언한 다음 props로 해당 변수의 이름을 넘겨주면 자동으로 **Motion**이 이를 연결해준다.
```jsx
const boxVariants = {
start: {
opacity: 0,
scale: 0.5,
},
end: {
opacity: 1,
scale: 1,
rotateZ: 360,
transition: {
type: "spring" as const,
bounce: 0.5,
},
},
};
<Box variants={boxVariants} initial="start" animate="end"/>
```
- **Motion**은 기본적으로 자식들이 부모의 애니메이션을 **복사해서** 적용되도록 만든다.
💡다시 말해 부모의 **initial, end**의 Props 값을 동일하게 가지고 있다는 말이고 이는 부모의 Variants 안의 변수명과 자식의 Variants 안의 변수명을 동일하게 가지고 있으면 굳이 따로 자식의 **initial** 값과 **end** 값을 지정하지 않아도 된다는 뜻이다.
```tsx
const boxVariants = {
start : {
opacity: 0,
scale: 0.5,
},
end : {
scale: 1,
opacity: 1,
transition: {
type: "spring" as const,
duration: 0.5,
bounce: 0.5,
delayChildren: 0.5,
staggerChildren: 0.2,
}
}
}
const circleVariants = {
start : {
opacity: 0,
y : 10,
},
end : {
opacity: 1,
y : 0,
}
}
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
</Box>
```
### Gestures
```jsx
const boxVariants = {
hover : { scale : 1.5, rotateZ : 90 },
tap : { scale : 1, borderRadius : "100%" },
}
// hover와 tap도 Variants로 설정 가능
<Box variants={boxVariants} whileHover="hover" whileTap="tap" />
```
### Dragging
```jsx
const boxVariants = {
hover : { scale : 1.5, rotateZ : 90 },
tap : { scale : 1, borderRadius : "100%" },
drag : { backgroundColor : "rgba(255, 255, 255, 0.2)" , transition : { duration : 10 }},
}
<Box drag variants={boxVariants} whileHover="hover" whileTap="tap" whileDrag="drag"/>
```
- **Constraint**
```jsx
const BiggerBox = styled.div`
width: 600px;
height: 600px;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 40px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
`;
const boxVariants = {
hover : { scale : 1.5, rotateZ : 90 },
tap : { scale : 1, borderRadius : "100%" },
drag : { backgroundColor : "rgba(255, 255, 255, 0.2)" , transition : { duration : 10 }},
}
function Animation() {
const biggerBoxRef = useRef<HTMLDivElement>(null);
return (
<Wrapper>
<BiggerBox ref={biggerBoxRef}>
<Box drag dragSnapToOrigin dragElastic={0.5} dragConstraints={biggerBoxRef} variants={boxVariants} whileHover="hover" whileTap="tap" whileDrag="drag"/>
</BiggerBox>
</Wrapper>
);
}
```
### Scroll
- **useTransform**을 통해 값의 변환을 쉽게 구현할 수 있다.
```tsx
function Animation() {
const x = useMotionValue(0);
const rotateZ = useTransform(x, [-800, 800], [-360, 360]);
const gradient = useTransform(x, [-800, 800], ["linear-gradient(135deg, rgb(238, 0, 153), rgb(255, 105, 180))", "linear-gradient(135deg, rgb(255, 105, 180), rgb(238, 0, 153))"]);
return (
<Wrapper style={{ background : gradient }}>
<Box drag = "x" style={{ x, rotateZ }} dragSnapToOrigin/>
</Wrapper>
);
}
```
- **useScroll**을 통해 Scroll 정보를 쉽게 가져올 수 있다.
- **useScroll**에는 **scrollX**, **scrollXProgress**, scrollY, **scrollYProgress** 값이 있다.
```jsx
function Animation() {
const { scrollYProgress } = useScroll();
const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
return (
<Wrapper style={{ background : gradient }}>
<Box drag = "x" style={{ x, scale }} dragSnapToOrigin/>
</Wrapper>
);
}
```
### SVG Animation
- **stroke**와 **strokeWidth**를 통해 태두리 색과 굵기를 정할 수 있다.
- SVG에 **pathLength**을 통해 SVG Animation 효과를 줄 수 있다.
```jsx
const Svg = styled.svg`
width: 300px;
height: 300px;
color: white;
opacity: 0.4;
path {
stroke : white;
strokeWidth : 2;
}
`;
const svgVariants = {
start : {
pathLength : 0,
fill : "rgba(255, 255, 255, 0)"
},
end : {
pathLength : 1,
fill : "rgba(255, 255, 255, 1)"
}
}
<Svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 448 512">
<motion.path variants={svgVariants} initial = "start" animate="end" transition={{default : {duration : 5}, fill : {duration : 2, delay : 3}}} fill="transparent" d="M224 373.1c-25.2-31.7-40.1-59.4-45-83.2-22.6-88 112.6-88 90.1 0-5.5 24.3-20.3 52-45 83.2zm138.2 73.2c-42.1 18.3-83.7-10.9-119.3-50.5 103.9-130.1 46.1-200-18.9-200-54.9 0-85.2 46.5-73.3 100.5 6.9 29.2 25.2 62.4 54.4 99.5-32.5 36.1-60.6 52.7-85.2 54.9-50 7.4-89.1-41.1-71.3-91.1 15.1-39.2 111.7-231.2 115.9-241.6 15.8-30.1 25.6-57.4 59.4-57.4 32.3 0 43.4 25.9 60.4 59.9 36 70.6 89.4 177.5 114.8 239.1 13.2 33.1-1.4 71.3-37 86.6zm47-136.1C280.3 35.9 273.1 32 224 32c-45.5 0-64.9 31.7-84.7 72.8C33.2 317.1 22.9 347.2 22 349.8-3.2 419.1 48.7 480 111.6 480c21.7 0 60.6-6.1 112.4-62.4 58.7 63.8 101.3 62.4 112.4 62.4 62.9 .1 114.9-60.9 89.6-130.2 0-3.9-16.8-38.9-16.8-39.6z" />
</Svg>
```
> 💡 transition에서 직접 애니메이션 순서 밑 시간을 따로따로 설정 할 수 있다.
> ```jsx
> transition={{default : {duration : 5}, fill : {duration : 2, delay : 3}}}
> ```
### Slider
- **custom**을 통해 **Variants**안에 특정 변수로 애니메이션을 관리할 수 있다.
- 만약 **AnimatePresence**을 사용하는 경우 해당 **AnimatePresence** 안에도 **custom**을 넣어 주어야 한다.
- **custom을 사용하는 변수**들은 **함수**로 만들어주어야 한다.
```jsx
const boxVariants = {
entry: (isBack: boolean) => ({
x: isBack ? -500 : 500,
opacity: 0,
scale: 0,
}),
center: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
}
},
exit: (isBack: boolean) => ({
x: isBack ? 500 : -500,
opacity: 0,
scale: 0,
transition: {
duration: 1,
}
})
}
function Animation() {
const [visible, setVisible] = useState(1);
const [back, setBack] = useState(false);
const nextPlease = () => {
setBack(false);
setVisible((prev) => prev < 10 ? prev + 1 : 1);
}
const prevPlease = () => {
setBack(true);
setVisible((prev) => prev > 1 ? prev - 1 : 10);
}
return (
<Wrapper>
<AnimatePresence mode="wait" custom={back}>
<Box key={visible} variants={boxVariants} custom={back} initial="entry" animate="center" exit="exit">{visible}</Box>
</AnimatePresence>
<button onClick={nextPlease}>Next</button>
<button onClick={prevPlease}>Prev</button>
</Wrapper>
);
}
```
💡 **AnimatePresence**에서 `mode=”wait”` 을 통해 하나의 애니메이션이 끝나면 다른 애니메이션이 동작하도록 구현할 수 있다. (exitBeforeEnter X)
### Screen
```jsx
const Wrapper = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, rgb(238, 0, 153), rgb(255, 105, 180));
gap: 20px;
`;
const Box = styled(motion.div)`
height: 200px;
background-color: white;
border-radius: 15px;
display: flex;
justify-content: center;
align-items: center;
font-size: 28px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const Grid = styled.div`
display: grid;
div:first-child, div:last-child {
grid-column: span 2;
}
grid-template-columns: repeat(3, 1fr);
width: 50vw;
gap: 10px;
`;
const Overlay = styled(motion.div)`
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
`;
const boxVariants = {
}
function Animation() {
const [id, setId] = useState<string | null>(null);
return (
<Wrapper>
<Grid>
{[1,2,3,4].map((n) => (
<Box layoutId={n.toString()} key={n} onClick={() => setId(n.toString())}/>
))}
</Grid>
<AnimatePresence>
{id ? <Overlay initial={{backgroundColor: "rgba(0, 0, 0, 0)"}} animate={{backgroundColor: "rgba(0, 0, 0, 0.5)"}} exit={{backgroundColor: "rgba(0, 0, 0, 0)"}} onClick={() => setId(null)}>
<Box layoutId={id} style={{width: 400, height: 200}}/>
</Overlay> : null}
</AnimatePresence>
</Wrapper>
);
}
```
# Animation 라이브러리 종류
### **1. Framer Motion**
- 장점
- 사용하기 쉬운 API
- 높은 성능
- 물리 기반 애니메이션 및 제스처 제어 지원
- 풍부한 예제와 문서화
- **단점**
- 비교적 큰 파일 크기
### **2. React-Spring**
- **장점**
- 물리 기반 애니메이션 지원
- 직관적인 API
- 성능 최적화 기능
- 범용적으로 사용 가능
- **단점**
- 러닝 커브가 다소 높음
### **3. Remotion**
- **장점**
- 비디오 생성에 최적화
- 실시간 미리보기 지원
- 프레임 기반 애니메이션
- 타임라인 컴포넌트 제공
- **단점**
- 일반 웹 애니메이션에는 적합하지 않음
### **4. React Motion**
- **장점**
- 물리 기반 애니메이션 지원
- 간단한 API
- 부드러운 애니메이션
- 높은 성능
- **단점**
- 러닝 커브가 다소 높음
### **5. React Move**
- **장점**
- 선언적 애니메이션
- 데이터 시각화에 특화
- D3.js와 함께 사용 가능
- **단점**
- 데이터 시각화 외의 애니메이션에는 적합하지 않음
### **6. React-anime**
- **장점**
- 다양한 애니메이션 효과
- 타이밍 및 CSS 변형 지원
- Anime.js를 기반으로 함
- **단점**
- 다른 라이브러리에 비해 도큐멘테이션 및 예제가 부족함
### **7. React Awesome Reveal**
- **장점**
- 스크롤 시 애니메이션 지원
- 다양한 미리 정의된 애니메이션 효과
- 사용자 정의 애니메이션 구현 가능
- 쉬운 사용법
- **단점**
- 다른 라이브러리에 비해 기능이 제한적임
### **8. React Transition Group**
- **장점**
- 컴포넌트 전환 애니메이션 지원
- CSS와 조합하여 다양한 효과 구현 가능
- 선언적 API
- **단점**
- 기본 애니메이션 효과가 없음
### **9. GSAP (GreenSock Animation Platform)**
- **장점**
- 강력한 애니메이션 기능
- 다양한 애니메이션 효과, 타이밍, 이징 등의 기능 제공
- 크로스 브라우저 호환성
- 높은 성능
- **단점**
- 다른 React 애니메이션 라이브러리에 비해 러닝 커브가 높음