Framer-motion
은 React로 개발된 animation library이다.Framer-motion
은 Typescript를 지원하며, 최적화된 성능 및 풍부한 API를 제공한다$ yarn add framer-motion
<motion.div>Box</motion.div>
const Box = styled(motion.div)``;
or
<Box as={motion.div} />
<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" />;
// 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>
);
}
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>
);
}
<Box drag variants={boxVariants} whileHover="hover" whileTap="tap" />
컴포넌트 애니메이션에 color를 줄 때
blue
,black
과 같이 string 형태로 주면 transition이 동작안함. 값으로 입력해주어야 함. (rgba(1, 2, 3)
과 같이.)
// 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>
);
}
<Box
drag
dragConstraints={bigBoxRef}
dragSnapToOrigin
variants={boxVariants}
whileHover="hover"
whileTap="tap"
whileDrag={{
backgroundColor: "rgba(46, 204, 113)",
transition: { duration: 2 },
}}
/>
<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
를 사용const x = useMotionValue(0);
useMotionValueEvent(x, "change", (value) => {
console.log(value);
});
return (
<Container>
<Box style={{ x }} drag="x" dragSnapToOrigin />
</Container>
);
useTransform
은 추적할 값에 따라 값을 변경시켜줌const x = useMotionValue(0);
const scale = useTransform(x, [-300, 0, 300], [2, 1, 0.1]);
<motion.path initial={...} animate={...} />
<motion.path
variants={svgVar}
initial="start"
animate="end"
transition={{
default: { duration: 3 },
fill: { duration: 2, delay: 3 },
}}
stroke="white"
strokeWidth="2"
/>
// 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>;
// 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>;
true
와 false
에 따라 다른 애니메이션을 적용Element의 key를 바꿔주면 React는 element가 사라졌다고 판단한다.
<Container onClick={toggleClicked}>
<Box
style={{
justifyContent: clicked ? "center" : "flex-start",
alignItems: clicked ? "center" : "flex-start",
}}
>
<Circle layout />
</Box>
</Container>
<Container onClick={toggleClicked}>
<Box>{clicked && <Circle layoutId="circle" />} </Box>
<Box>{!clicked && <Circle layoutId="circle" />}</Box>
</Container>
import React, { useState } from "react";
import { styled } from "styled-components";
import { AnimatePresence, motion } from "framer-motion";
function App() {
const [clickedId, setClickedId] = useState<null | string>(null);
return (
<Container>
<GridContainer>
{[1, 2, 3, 4].map((i) => (
<Box
onClick={() => setClickedId(String(i))}
key={i}
layoutId={String(i)}
/>
))}
</GridContainer>
<AnimatePresence>
{clickedId && (
<Overlay
onClick={() => setClickedId(null)}
initial={{ background: "rgba(0, 0, 0, 0)" }}
animate={{ background: "rgba(0, 0, 0, 0.5)" }}
exit={{ background: "rgba(0, 0, 0, 0)" }}
>
<OverlayBox layoutId={clickedId} />
</Overlay>
)}
</AnimatePresence>
</Container>
);
}
export default App;
const Container = styled(motion.div)`
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
background: linear-gradient(135deg, rgb(238, 0, 153), rgb(221, 0, 238));
`;
const GridContainer = styled.div`
display: grid;
width: 80vw;
gap: 10px;
grid-template-columns: repeat(3, 1fr);
div:first-child,
div:last-child {
grid-column: span 2;
}
`;
const Box = styled(motion.div)`
height: 200px;
background-color: white;
border-radius: 30px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
`;
const OverlayBox = styled(Box)`
width: 400px;
height: 300px;
`;
const Overlay = styled(motion.div)`
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
`;
참고 :
Framer-motion Document
singsoong github: Framer-motion practice repository
React Framer Motion 톺아보기
노마드코더: 마스터 클래스
좋은 글 감사합니다.