# **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 애니메이션 라이브러리에 비해 러닝 커브가 높음