npm i framer-motion
react-create-app
ver.5 부터 정식 지원한다.react-create-app
ver.4 을 사용하여 호환되지 않는 에러 발생 시 npm i @craco/craco --save
를 설치해 필요한 모듈을 오버라이드한다.CRACO
는 C
reate R
eact A
pp C
onfiguration O
verride의 약자로, craco.config.js
을 추가해 필요한 모듈 상세를 사용자 지정으로 사용할 수 있다.// craco.config.js
module.exports = {
webpack: {
configure: {
module: {
rules: [
{
type: "javascript/auto",
test: /\.mjs$/,
include: /node_modules/,
},
],
},
},
},
};
import { motion } from "framer-motion";
import { useState } from "react";
function App() {
const [toggleOpa, setToggleOpa] = useState(false);
return (
<div>
<motion.div
animate={{ opacity: toggleOpa ? 1 : 0 }}
style={{ width: 200, height: 200, backgroundColor: "red" }}
>
Hi!
</motion.div>
<button onClick={() => setToggleOpa((prev) => !prev)}>
Are you visible?
</button>
</div>
);
}
export default App;
<motion.{element}>
를 만든 후 속성을 추가한다.
styled components
와 결합하여 사용할 수 있다.
import styled from "styled-components";
import { motion } from "framer-motion";
const Box = styled(motion.div)`
width: 200px;
height: 200px;
background-color: white;
border-radius: 10px;
`;
function App() {
return (
<div>
<Box animate={{ opacity: toggleOpa ? 1 : 0 }} />
<button onClick={() => setToggleOpa((prev) => !prev)}>
Are you visible?
</button>
</div>
);
}
export default App;
initial
로 설정한다.<motion.div initial={{ scale: 0 }} />
animate
로 설정한다.<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} />
transition
으로 설정한다.<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring" }}
/>
transition
을 따로 설정할 수 있다.const vars = {
start: { opacity: 0, scale: 1 },
end: {
opacity: 1,
scale: 1.5,
transition: {
default: { duration: 2 },
scale: { duration: 2, delay: 2 },
},
},
};
const App = () => {
return (
<motion.div
style={{ width: 200, height: 200, backgroundColor: "white" }}
variants={vars}
initial="start"
animate="end"
/>
);
};
variants
는 다른 여러 애니메이션을 하나의 애니메이션으로 사용할 수 있게 한다.const vars = {
start:{ scale: 0 },
end:{
scale: 1,
transition={
type: "spring"
}
}
}
<motion.div variants={vars} initial="start" animate="end" />
variants
도 설정할 수 있다.variants
의 initial(start)
과 animate(end)
속성은 자동으로 자식 요소에게 상속된다,variants
속성명을 같게 하면 컴포넌트에 적지 않아도 된다.delay
등 시간을 다르게 설정하고 싶다면 부모 variants
의 end:{transition}
에 staggerChildren
을 추가한다.const parentVars = {
start:{ scale: 0 },
end:{
scale: 1,
transition={
type: "spring"
staggerChildren: 0.2,
}
}
}
const childrenVars {
start: { opacity: 0 },
end: { opacity: 1 },
}
<motion.div variants={parentVars} initial="start" animate="end">
{[1, 2, 3, 4].map((v) => (
<motion.div key={v} variants={circleVariants} />
))}
</motion.div>
mouseEvent
와 섞을 때는 간단히 while~~
속성으로 사용할 수 있다.<motion.div
whileHover={{ scale: 1.5, rotateZ: 90 }}
whileTap={{ borderRadius: "100px", scale: 1 }}
/>
whileHover
는 마우스을 hover
했을 때, whileTap
은 마우스를 클릭하고 있을 때 동작한다.
variants
와 결합하여 사용할 수 있다.
const vars = {
hover: { scale: 1.5, rotateZ: 90 },
click: { borderRadius: "100px", scale: 1 },
}
<motion.div variants={vars} whileHover="hover" whileTap="click" />
drag
속성을 추가하면 요소를 드래그할 수 있다.whileDrag
로 드래그하는 동안 애니메이션을 추가할 수 있다.rbga
값으로 넣어야 애니메이션이 적용된다.<motion.div drag whileDrag={{ backgroundColor: "rgba(255, 159, 67, 1.0)" }} />
drag
에 x
나 y
등으로 축을 제약할 수 있다.<motion.div drag="x" />
<motion.div drag="y" />
drag
제약을 사용자 지정으로 설정하려면 dragConstraints
로 설정한다.<motion.div
drag
dragConstraints={{ top: -50, bottom: 50, left: -50, right: 50 }}
/>
dragSnapToOrigin
을 추가한다.<motion.div
drag
dragSnapToOrigin
dragConstraints={{ top: -50, bottom: 50, left: -50, right: 50 }}
/>
motion
이 움직이는 값을 useMotionValue
로 추적할 수 있다.function App() {
const x = useMotionValue(0);
console.log(x);
return (
<>
<motion.div drag="x" dragSnapToOrigin style={{ x }} />
</>
);
}
export default App;
console.log(x)
는 아무것도 반환하지 않는다.motionValue
는 React State
가 아니기 때문에 re-render
하지 않기 때문이다.useEffect
를 통해 확인할 수 있다.function App() {
const x = useMotionValue(0);
useEffect(() => {
x.onChange(() => console.log(x.get()));
}, [x]);
return (
<>
<motion.div drag="x" dragSnapToOrigin style={{ x }} />
</>
);
}
export default App;
x
의 설정은 x.set()
으로 한다.useTransform
은 motionValue
에 따른 옵션 설정이 가능한 기능이다.축
, 축의 값[]
, 변환할 값[]
)을 받는다.const x = useMotionValue(0);
const tmpScale = useTransform(x, [-300, 0, 300], [2, 1, 0.1]);
<motion.div drag="x" dragSnapToOrigin style={{ x, scale: tmpScale }} />;
useScroll
은 스크롤의 위치를 반환한다.scrollX || scrollY
는 스크롤의 픽셀 좌표를 나타내고, scrollXProgress || scrollYProgress
는 현재 스크롤이 위치한 퍼센티지를 나타낸다.const { scrollY, scrollYProgress } = useScroll();
useEffect(() => {
scrollY.onChange(() => {
console.log(scrollY.get(), scrollYProgress.get());
// 198 0.31118314424635335
});
}, [scrollY]);
<AnimatePresence>
을 사용한다.<AnimatePresence>
안에는 조건 분기가 존재해야 한다.initial
과 animate
입력은 다른 것과 같고, 분기 판단 속성으로 exit
을 추가한다.function App() {
const [visible, setVisible] = useState(false);
const onToggle = () => setVisible((prev) => !prev);
return (
<>
<button onClick={onToggle}>Click</button>
<AnimatePresence>
{visible ? (
<Box
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
) : null}
</AnimatePresence>
</>
);
}
variants
조정은 custom
속성을 사용한다.custom
을 사용할 때는 variants
의 속성이const vars = {
init: (direction) => ({
opacity: 0,
x: direction ? 300 : -300,
}),
animate: {
opacity: 1,
},
exit: (direction) => ({
opacity: 0,
x: direction ? -300 : 300,
}),
};
function App() {
const [visible, setVisible] = useState(false);
const onToggle = () => setVisible((prev) => !prev);
return (
<>
<button onClick={onToggle}>Click</button>
<AnimatePresence>
<Box initial="init" animate="animate" exit="exit" />
</AnimatePresence>
</>
);
}
layout
속성은 layout
이 바뀔 때 애니메이션을 제공한다.const Box = styled(motion.div)`
width: 400px;
height: 400px;
background-color: white;
border-radius: 20px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
`;
const Circle = styled(motion.div)`
background-color: black;
height: 100px;
width: 100px;
border-radius: 50px;
`;
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<div onClick={toggleClicked}>
<Box
style={{
justifyContent: clicked ? "center" : "flex-start",
alignItems: clicked ? "center" : "flex-start",
}}
>
<Circle layout />
</Box>
</div>
);
}
export default App;
layoutId
가 같으면 다른 컴포넌트더라도 같은 레이아웃을 사용하는 것처럼 애니메이션을 적용할 수 있다.const Box = styled(motion.div)`
width: 400px;
height: 400px;
background-color: white;
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
`;
const Circle = styled(motion.div)`
background-color: black;
height: 100px;
width: 100px;
border-radius: 50px;
`;
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<div onClick={toggleClicked}>
<Box>{!clicked ? <Circle layoutId="circle" /> : null}</Box>
<Box>{clicked ? <Circle layoutId="circle" /> : null}</Box>
</div>
);
}
export default App;
https://www.framer.com/docs/animate-shared-layout/#syncing-layout-animations
const vars = {
normal: {
opacity: 0,
},
infinite: {
opacity: [0, 1, 0],
transition: {
repeat: Infinity,
},
},
};
function App() {
const [visible, setVisible] = useState(false);
const onToggle = () => setVisible((prev) => !prev);
return (
<>
<button onClick={onToggle}>Click</button>
<AnimatePresence>
<Box initial="normal" whileHover="infinite" />
</AnimatePresence>
</>
);
}
transition
의 repeat
은 정해진 값만큼 반복하는데, 그 값으로 javascript
내장값인 Infinity
를 주면 무한히 반복한다.참고