인터렉티브한 애플리케이션은 사용자의 경험에 큰 도움이 됩니다. 흥미롭게 반응하는 UI는 사용자가 애플리케이션에 머무는 시간을 늘려주고 제공하는 서비스는 더욱 신뢰성 있게 보입니다. 이는 경쟁하고 있는 타 애플리케이션과는 차별화된 기능이 될 것입니다.
framer-motion
은 React
에서 애니메이션과 제스쳐를 쉽게 다룰 수 있도록 해주는 라이브러리입니다. css
를 이용해 힘들게 만들었던 애니메이션을 framer-motion
은 아주 손쉽게 만들 수 있습니다.
framer-motion
을 사용하기 위해서는 motion
을 통해서 엘리먼트를 생성해야 합니다.
import { motion } from 'framer-motion'
funtion App() {
return <motion.div />
}
styled-components
와 같은 라이브러리를 사용하고 있더라고 motion
을 이용해서 엘리먼트를 사용하면 framer-motion
을 사용할 수 있습니다.
const Box = styled(motion.div)``
motion
컴포넌트의 prop을 통해 애니메이션을 실행할 수 있습니다.
초기 속성을 지정하는 prop입니다. boolean값인 false
값으로 설정한다면 애니메이션이 비활성화됩니다.
<motion.div initial={{ opacity: 0 }} />
애니메이션으로 지정할 값을 설정하는 prop입니다.
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
애니메이션의 transtion을 지정하는 prop입니다.
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", bounce: 0.5, delay: 1 }}
/>
variants
를 사용하여 코드를 정돈할 수 있습니다. 객체형태로 구성되어 motion
컴포넌트의 prop을 이름별로 구분하여 사용할 수 있으며, motion
컴포넌트의 variants
prop에 해당 객체로 지정 후 각각의 prop에 구분된 이름을 넣어 사용할 수 있습니다. 상위 컴포넌트와 하위 컴포넌트의 variants
의 구성 요소의 이름이 같다면 상위 컴포넌트에만 prop의 이름을 지정해도 하위 컴포넌트의 애니메이션이 실행됩니다.
const boxVariants = {
start: { opacity: 1, scale: 0 },
end: {
opacity: 1,
scale: 1,
},
};
const circleVariants = {
start: { opacity: 0, y: 20 },
end: {
opacity: 1,
y: 0,
},
};
function App() {
return (
<Wrapper>
<Box
transition={{ type: "spring", bounce: 0.5, delay: 1 }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
</Box>
</Wrapper>
);
}
상위 컴포넌트 variants
의 transition
에서 자식들의 애니메이션을 조정할 수 있습니다.
delayChildren
를 통해 자식 컴포넌트의 애니메이션을 지연시킬 수 있으며, staggerChildren
를 통해 자식들간의 애니메이션 간격을 조정할 수 있습니다.
const boxVariants = {
start: { opacity: 1, scale: 0 },
end: {
opacity: 1,
scale: 1,
transition: {
delayChildren: 0.3,
staggerChildren: 0.2,
},
},
};
마우스 상태에 따른 이벤트에 반응하는 애니메이션을 실행할 수 있습니다.
<motion.button
whileHover={{
scale: 1.2,
transition: { duration: 1 },
}}
whileTap={{ scale: 0.9 }}
/>
drag
prop을 입력하는 것만으로 엘리먼트를 마우스 드레그로 이동시킬 수 있습니다.
<motion.div drag />
// x축만 드레그 가능
<motion.div drag="x" />
// y축만 드레그 가능
<motion.div drag="y" />
dragconstraints
를 통해 드레그 구역을 제한할 수 있습니다.
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 300 }}
/>
// useRef를 활용하면 부모 컴포넌트 내에서만 드레그 할 수 있게 만들 수 있습니다.
function App() {
const biggerBoxRef = useRef(null);
return (
<BiggerBox ref={biggerBoxRef}>
<Box
drag
// 드레그 후 다시 돌아오게 합니다.
dragSnapToOrigin
// 드레그 강도를 조절합니다.
dragElastic={0}
dragConstraints={biggerBoxRef}
/>
</BiggerBox>
);
}
MotionValue
는 애니메이션 내의 수치를 트래킹할때 사용합니다. useMotionValue
를 통해 상태를 생성할 수 있습니다. Motionvalue
는 업데이트시 리엑트 렌더링 싸이클을 발동하지 않습니다. useMotionValueEvent
를 통해서 상태의 변화를 확인할 수 있습니다. 또한 useTransform
를 사용하면 Motionvalue
값에 따라 새로운 값을 출력할 수 있습니다.
그 외에도 useScroll
, useTime
등을 이용하여 다양한 Motionvalue
를 이용할 수 있습니다.
function App() {
const x = useMotionValue(0);
const { scrollY, scrollYProgress } = useScroll();
const rotate = useTransform(x, [-800, 800], [360, -360]);
const gradient = useTransform(
x,
[-800, 0, 800],
[
"linear-gradient(135deg, rgb(0,210,238), rgb(0,83,238))",
"linear-gradient(135deg, rgb(238,0,153), rgb(221,0,238))",
"linear-gradient(135deg, rgb(0,238,155), rgb(238,178,0))",
]
);
const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
useMotionValueEvent(x, "change", () => {
console.log(gradient.get());
});
useMotionValueEvent(scrollY, "change", () => {
console.log(scrollY.get(), scrollYProgress.get());
});
return (
<Wrapper style={{ background: gradient }}>
<Box style={{ x, rotate, scale }} drag="x" dragSnapToOrigin />
</Wrapper>
);
}
svg
, path
엘리먼트로 이루어진 SVG를 pathLength
, fill
css속성을 사용한다면 손쉽게 애니메이트할 수 있습니다.
const svgVariants = {
start: { pathLength: 0, opacity: 0 },
end: {
pathLength: 1,
opacity: 1,
transition: { default: { duration: 5 }, opacity: { duration: 3 } },
},
};
function App() {
return (
<Wrapper>
<motion.svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<motion.path
variants={svgVariants}
initial="start"
animate="end"
stroke="white"
strokeWidth={1}
stroke-linecap="round"
stroke-linejoin="round"
/>
</motion.svg>
</Wrapper>
);
}
Animatepresence
컴포넌트는 React
에서 사라지는 컴포넌트를 애니메이트할 수 있습니다.
Animatepresence
컴포넌트 내부에 조건문에 따른 Motion
컴포넌트가 존재한다면 exit
prop을 이용해 사라지는 애니메이트가 가능합니다. 이외에도 Animatepresence
컴포넌트에 mode
와 같은 속성을 사용하여 애니메이트가 가능합니다.
const boxVariants = {
initial: { opacity: 0, scale: 0, rotateZ: 45 },
visible: { opacity: 1, scale: 1, rotateZ: 0 },
leaving: { opacity: 0, scale: 0, y: 100 },
};
function App() {
const [isShowing, setIsShowing] = useState(false);
return (
<Wrapper>
<button onClick={() => setIsShowing((pre) => !pre)}>click</button>
<AnimatePresence>
{isShowing && (
<motion.div
variants={boxVariants}
initial="initial"
animate="visible"
exit="leaving"
/>
)}
</AnimatePresence>
</Wrapper>
);
}
custom
은 variant
객체에 데이터를 보내주는 속성입니다. variant
객체는 함수 형태로 custom
을 받아와 사용할 수 있습니다. Animatepresence
컴포넌트를 사용한다면 Animatepresence
컴포넌트 속성에도 cutom
값을 넣어주어야 합니다.
const boxVariants = {
entry: (isBack) => ({
opacity: 0,
scale: 0,
x: isBack ? -300 : 300,
rotateZ: isBack ? -10 : 10,
}),
center: {
opacity: 1,
scale: 1,
x: 0,
rotateZ: 0,
transition: { duration: 0.3 },
},
exit: (isBack) => ({
opacity: 0,
scale: 0,
x: isBack ? 300 : -300,
rotateZ: isBack ? 10 : -10,
transition: { duration: 0.3 },
}),
};
function App() {
const [visible, setVisible] = useState(1);
const [isBack, setIsBack] = useState(false);
const nextVisible = () => {
setVisible((prev) => (prev === 10 ? 10 : prev + 1));
setIsBack(false);
};
const preVisible = () => {
setVisible((prev) => (prev === 1 ? 1 : prev - 1));
setIsBack(true);
};
return (
<Wrapper>
<AnimatePresence mode="wait" custom={isBack}>
<Box
key={visible}
custom={isBack}
variants={boxVariants}
initial="entry"
animate="center"
exit="exit"
>
{visible}
</Box>
</AnimatePresence>
<button onClick={preVisible}>pre</button>
<button onClick={nextVisible}>next</button>
</Wrapper>
);
}
layout
은 css의 변화에 애니메이션을 부여합니다. 단순이 Motion
컴포넌트에 layout
속성을 넣으면 css의 변화를 애니메이트가 가능합니다. 더불어 layoutId
를 사용한다면 서로 다른 Motion
컴포넌트를 서로를 공유하여 애니메이트가 가능합니다.
function App() {
const [isClicked, setIsClicked] = useState(false);
const toggleClick = () => setIsClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClick}>
<motion.div>
{isClicked && (
<Circle layoutId="circle" style={{ borderRadius: "50%", scale: 2 }} />
)}
</motion.div>
<motion.div>
{!isClicked && <Circle layoutId="circle" style={{ borderRadius: 0 }} />}
</motion.div>
</Wrapper>
);
}
Framer Motion은 정말 간편하게 멋있는 애니메이션을 만들어 줍니다. 글을 작성하다보니 작성한 내용뿐만 아니라 더욱 다양한 기능을 제공하는 해당 라이브러리를 좀 더 사용해보고 싶었습니다. 물론 라이브러리를 완벽히 이해하고 사용하려면 바닐라 상태에서의 css 또한 완벽히 이해해야 한다는 생각이 들었습니다.