$ yarn add framer-motion
<motion.div>Box</motion.div>
- styled-components
const Box = styled(motion.div)``;
or
<Box as={motion.div} />
- Basic Animation
<Box
transition={{ type: "spring", delay: 0.3 }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
// Variants
const myVars = {
start: { scale: 0 },
end: { scale: 1, rotateZ: 360, transition: { type: "spring", delay: 0.3 } },
};
<Box variants={myVars} initial="start" animate="end" />;
- delayChildren: ๋ชจ๋ ์์๋ค์๊ฒ delay ๊ฑฐ๋ ์์ฑ
- staggerChildren: ๊ฐ ์์๋ง๋ค ์์ฐจ์ ์ผ๋ก ์คํํ ๋ ์ฌ์ฉํ๋ ์์ฑ
// Variants
const boxVariants = {
start: {
opacity: 1,
scale: 0,
},
end: {
opacity: 1,
scale: 1,
transition: {
delayChildren: 0.3,
staggerChildren: 0.2,
},
},
};
const circleVariants = {
start: {
opacity: 0,
y: 20,
},
end: {
opacity: 1,
y: 0,
},
};
function App() {
return (
<Container>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
</Box>
</Container>
);
}
- whileHover, whileTap๊ณผ ๊ฐ์ props๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ์กฐ๊ฑด์์์ ์ ๋๋ฉ์ด์ ์ ์ฒ๋ฆฌํ ์ ์์.
const boxVariants = {
hover: { scale: 1.5, rotateZ: 90 },
tap: { borderRadius: "150px", scale: 1 },
};
function App() {
return (
<Container>
<Box variants={boxVariants} whileHover="hover" whileTap="tap"></Box>
</Container>
);
}
- drag ์์ฑ์ ์ค์ ์ปดํฌ๋ํธ๋ฅผ drag ํ ์ ์์
<Box drag variants={boxVariants} whileHover="hover" whileTap="tap" />
์ปดํฌ๋ํธ ์ ๋๋ฉ์ด์ ์ color๋ฅผ ์ค ๋
blue
,black
๊ณผ ๊ฐ์ด string ํํ๋ก ์ฃผ๋ฉด transition์ด ๋์์ํจ. ๊ฐ์ผ๋ก ์ ๋ ฅํด์ฃผ์ด์ผ ํจ. (rgba(1, 2, 3)
๊ณผ ๊ฐ์ด.)
- drag constraints: dragConstraints ์์ฑ์ ํตํด ๋๋๊ทธ ์์ญ์ ์ ํํ ์ ์์
- ๋ฒ์๋ฅผ ์ง์ ํ ๋ top, left, bottom, right์ ๊ฐ์ด ๊ฐ์ ์ ๋ ฅํ ์ ์์
- element์ ref๋ฅผ ๊ฐ์ ธ์์ dragConstraints์ ๊ฐ์ผ๋ก ์ฃผ์ด ์ ํํ ์๋ ์์
// value
<Box
drag
dragConstraints={{ top: -100, bottom: 100, left: -100, right: 100 }}
dragSnapToOrigin
variants={boxVariants}
whileHover="hover"
whileTap="tap"
whileDrag={{
backgroundColor: "rgba(46, 204, 113)",
transition: { duration: 2 },
}}
/>;
// ref
function App() {
const bigBoxRef = useRef < HTMLDivElement > null;
return (
<Container>
<BigBox ref={bigBoxRef}>
<Box
drag
dragConstraints={bigBoxRef}
dragSnapToOrigin
variants={boxVariants}
whileHover="hover"
whileTap="tap"
whileDrag={{
backgroundColor: "rgba(46, 204, 113)",
transition: { duration: 2 },
}}
/>
</BigBox>
</Container>
);
}
- dragSnapToOrigin ์์ฑ์ ์ฃผ๋ฉด ๋๋๊ทธ ํ ์ ์๋ฆฌ๋ก ๋์๊ฐ
<Box
drag
dragConstraints={bigBoxRef}
dragSnapToOrigin
variants={boxVariants}
whileHover="hover"
whileTap="tap"
whileDrag={{
backgroundColor: "rgba(46, 204, 113)",
transition: { duration: 2 },
}}
/>
- dragElastic: ๋๋๊ทธํ ๋, ๋น๊ธฐ๋ ํ์ ์กฐ์ ํ ์ ์์ (default: 0.5)
<Box
drag
dragConstraints={bigBoxRef}
dragSnapToOrigin
dragElastic={1}
variants={boxVariants}
whileHover="hover"
whileTap="tap"
whileDrag={{
backgroundColor: "rgba(46, 204, 113)",
transition: { duration: 2 },
}}
/>
useMotionValue
๋ฅผ ์ฌ์ฉํด ๊ฐ์ ์ ์ธํ๊ณ , ์ด ๊ฐ์ ์ถ์ ํ element์ ์ฐ๊ฒฐํ ์ ์์- ์ถ์ ํ ๋์๋
useMotionValueEvent
๋ฅผ ์ฌ์ฉ - x์ ๊ฐ์ด ๋ณํ ๋ ํด๋น ์ปดํฌ๋ํธ๋ ๋ฆฌ๋ ๋๋ง ๋๋ ๋ฐฉ์์ด ์๋, ๋จ์ํ x์ ๊ฐ๋ง ๋ฐ๋
const x = useMotionValue(0);
useMotionValueEvent(x, "change", (value) => {
console.log(value);
});
return (
<Container>
<Box style={{ x }} drag="x" dragSnapToOrigin />
</Container>
);
useTransform
์ ์ถ์ ํ ๊ฐ์ ๋ฐ๋ผ ๊ฐ์ ๋ณ๊ฒฝ์์ผ์ค- ์๋ฅผ ๋ค์ด, x์ ์ขํ์ ๋ฐ๋ผ scale์ ๊ฐ์ด ๋ณ๊ฒฝ๋์์ผ๋ฉด ํจ
- x๊ฐ์ด -300์ผ ๋ 2, 0์ผ ๋ 1, 300์ผ ๋ 0.1์ด ๋ฆฌํด๋์์ผ๋ฉด ํจ
- ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ถ์ ํ ๊ฐ, ๋๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ์ถ์ ํ ๊ฐ์ ๊ธฐ์ค๊ฐ, ์ธ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก ๊ธฐ์ค๊ฐ์ ๋๋ฌํ์ ๋ ํ ๋นํ ๊ฐ์ ์
๋ ฅ
- ๋ฐ๋ผ์ input๊ณผ output์ ๊ฐ์๊ฐ ๊ฐ์์ผํจ
const x = useMotionValue(0);
const scale = useTransform(x, [-300, 0, 300], [2, 1, 0.1]);
- scrollX, scrollY: ํฝ์ ๋จ์๋ก ์คํฌ๋กค ๊ฐ์ ๊ฐ์ ธ์ฌ ์ ์์
- scrollXProgress, scrollYProgress: ์ด ์คํฌ๋กค ํ ์ ์๋ ๊ฐ์ ๋น์จ์ ๋ฆฌํด (์ต์๊ฐ 0, ์ต๋๊ฐ 1)
- motion์ pathํ๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ animation์ ๋ถ์ฌํ ์ ์์
<motion.path initial={...} animate={...} />
- ๊ฐ animation์ transition์ ๋ฐ๋ก๋ฐ๋ก ๋ถ์ฌํ ์ ์์
<motion.path
variants={svgVar}
initial="start"
animate="end"
transition={{
default: { duration: 3 },
fill: { duration: 2, delay: 3 },
}}
stroke="white"
strokeWidth="2"
/>
- AnimatePresence๋ฅผ ์ฌ์ฉํด์ React ํธ๋ฆฌ์์ ์ปดํฌ๋ํธ๊ฐ ์์ฑ๋๊ณ ์ ๊ฑฐ๋ ๋์ animation์ ๋ถ์ฌํ ์ ์์
- exit ์์ฑ์ ํตํด ์ข ๋ฃ๋ ๋ animation์ ์ค์ ํ ์ ์์
// Variants
const boxVar = {
start: {
opacity: 0,
scale: 0,
},
end: {
opacity: 1,
scale: 1,
rotateZ: 360,
},
leaving: {
opacity: 0,
scale: 0,
y: 100,
},
};
<AnimatePresence>
{isShow ? (
<Box variants={boxVar} initial="start" animate="end" exit="leaving" />
) : null}
</AnimatePresence>;
- custom: ๊ฐ animation ์ปดํฌ๋ํธ์ ๋ํด ๋์ variants๋ฅผ ์ ์ฉ์ํฌ ์ ์๋ ์์ฑ
// Variants
const boxVar = {
invisible: (isBack: boolean) => {
return {
x: isBack ? -300 : 300,
opacity: 0,
scale: 0,
};
},
visible: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 0.3,
},
},
exit: (isBack: boolean) => {
return {
x: isBack ? 300 : -300,
opacity: 0,
scale: 0,
transition: {
duration: 0.3,
},
};
},
};
<AnimatePresence custom={back}>
<Box
custom={back}
variants={boxVar}
initial="invisible"
animate="visible"
exit="exit"
key={visible}
>
{visible}
</Box>
</AnimatePresence>;
- isBack์ด
true
์false
์ ๋ฐ๋ผ ๋ค๋ฅธ ์ ๋๋ฉ์ด์ ์ ์ ์ฉ
Element์ key๋ฅผ ๋ฐ๊ฟ์ฃผ๋ฉด React๋ element๊ฐ ์ฌ๋ผ์ก๋ค๊ณ ํ๋จํ๋ค.
- Element์ layout ์์ฑ์ ๋ถ์ฌํ๋ฉด ํด๋น element์ css ์์ฑ์ด ๋ณ๊ฒฝ ๋ ๋ ๋ง๋ค animation์ ๋ถ์ฌํ๋ค.
- CSS์ ๋ณํ๊ฐ ์๋์ผ๋ก animate ๋๋ ๊ฒ
<Container onClick={toggleClicked}>
<Box
style={{
justifyContent: clicked ? "center" : "flex-start",
alignItems: clicked ? "center" : "flex-start",
}}
>
<Circle layout />
</Box>
</Container>
- ๊ฐ element์ layoutId๋ฅผ ๋ถ์ฌํ์ฌ element๋ฅผ ์ฐ๊ฒฐํ๊ณ ๋ element ์ฌ์ด์ css ๋ณํ๋ฅผ animation์ผ๋ก ์ฐ๊ฒฐ์์ผ์ค๋ค
<Container onClick={toggleClicked}>
<Box>{clicked && <Circle layoutId="circle" />} </Box>
<Box>{!clicked && <Circle layoutId="circle" />}</Box>
</Container>