: animation을 쉽게 구현할 수 있음.
📎 설치
npm i framer-motion
📎 import
import {motion} from 'framer-motion'
📎 규칙
animate 적용할 요소를 <motion.div>로 작성
📎 animate된 스타일드컴포넌트
const Box = styled(motion.div)``;
https://www.framer.com/developers/ > API Documentation
- animation할 속성 설정
- animation을 특정시킬 수 있음.
👾 #01
const Box = styled(motion.div)`...border-radius: 10px;`;
...
<Box
transition={{ duration: 3, delay:2 }}
animate={{ borderRadius: "100px" }}>
</Box>
...
👉🏻 2초 지나고 3초동안 사각형에서 원이됨.
- element의 초기 상태를 지정.
👾 #02
const Box = styled(motion.div)`...border-radius: 10px;`;
...
<Box
initial={{ scale:0 }}
animate={{ scale:1, rotateZ:360 }}
transition={{type:"spring" stiffness:1, damping:1, bounce: 0.5}}>
</Box>
...
👉🏻 처음에 el이 없다가 360도 회전하면서 나타남.
👉🏻 spring(기본값)은 현실세계의 물리법칙을 시뮬레이트함.
👉🏻 stiffness: 물리현상을 시뮬레이트함.
👉🏻 damping: 반동력, 0으로 하면 무기한으로 진동함.
👉🏻 bounce: 튐김의 수치를 정함. 기본값 0.25
👉🏻 모든 animation에는 기본적으로 spring(=Ease)이 기본적으로 적용되기 때문에 type:"tween"으로 선형적 타입으로 바꾸면 튕기는 효과가 사라짐.
- 많은 animation을 하나로 연결시켜 코드를 깔끔하게 만듬.
- animation의 무대
- 원하면 마지막 stage에 transition을 설정할 수 있음.
- start,end 네이밍은 사용자가 지정가능
- variants객체 생성.
- initial, animate 프롭에 varianst프롭에 설정한 variants객체의 프로퍼티 지정.
👾 #03
const myVars = {
start: { scale: 0 }, // initial stage
end: {
scale: 1,
rotateZ: 360,
transition:{ type: "spring", delay: 0.5 }
} // finishing stage
};
...
<Box
variants={myVars}
initial="start"
animate="end"
/>
...
👾 #04_자식들이 있는 경우
const boxVariants = {
start: { opsity:0, scale: 0.5 },
end: {
opsity:1, scale: 1,
transition:{
type:"spring",
duration:0.5,
bounce:0.5,
delayChildren: 0.5 // 자식요소들에게 delay적용
staggerChildren: 0.5 // 자식요소들에게 순서대로 증감하는 delay적용
} },
}
const circleVariants = {
start: { opsity: 0, y:10 },
end: { opsity: 1, y:0 },
}
...
<Box
variants={boxVariants}
initial="start"
animate="end"
>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
</Box>
...
👉🏻 자식 요소 motion객체의 start,end네이밍을 부모요소와 같에 지어야함.
👉🏻 기본적으로 motion은 initial="start", animate="end"을 자식 요소에 각각 붙여넣기 하기때문에 variants만 설정하면 됨.
- 요소위에 마우스를 hover, click등을 했을때 animation지정
👾 #05
const Box = styled(motion.div)`
width: 200px;
height: 200px;
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1)
`;
...
<Box
whileHover={{ scale: 1.5, rotateZ:90 }}
whileTap={{ scale:1, borderRadius: "100px" }}
/>
...
👾 #06_variants 적용
const boxVariants = {
hover: {scale: 1.5, rotateZ:90},
click: {scale:1, borderRadius: "100px"}
}
...
<Box
variants={boxVariants}
whileHover="hover"
whileTap="click"
/>
- 어떤 방향이든 드래그하고, 특정 범위 내에서 드래그할 때의 animation지정
👾 #07_떤 방향이든 드래그
<Box
drag // 속박없이 움직일 수 있음.
whileDrag={{backgroundColor:"rgb(255, 234, 167)"}} // 드래그 동안 스타일 지정
variants={boxVariants}
whileHover="hover"
whileTap="click"
/>
👉🏻 드래그 하는 동안 요소의 배경색이 노란색으로 바뀜
👉🏻 색변경 시 animation적용하기 위해서는 색상을 정수로 표현된걸로 지정해야함.
👉🏻 drag="x"로 지정하면 x축으로만 드래그 할 수 있음.
- dragConstraints: 드래깅이 허용될 수 있는 영역 지정.
- useRef를 사용하여 범위를 지정할 수 있음.
- dragSnapToOrigin : 원래 위치로 돌아오게 함.
- dragElastic: 당기는 힘. 0~1사이의 값(기본값 0.5), 수치가 클 수록 마우스포인터에 맞게 드래그가 실행 됨. 0으로 지정하면 범위 안에서만 움직임(overflow하지 않음).
👾 #07_특정 범위 내에서 드래그
...
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;
`;
...
const biggerBoxRef = useRef(null);
...
<BiggerBox ref={biggerBoxRef}>
<Box
drag // 드래그 활성화
dragSnapToOrigin // 드롭 시 중앙으로 돌아오는 프롭(원래 위치로 돌아옴.)
dragElastic={1} // 드래그 시 당기는 힘, 0으로 지정하면 벽안에서만 움직임.
dragConstraints={biggerBoxRef} // 제약 설정
variants={boxVariants}
whileHover="hover"
whileTap="click"
/>
</BiggerBox>
- 애니매이션 내의 수치를 트래킹할 때 필요
- 드래깅하는 요소를 추적할 때 필요
- MotionValue값은 set메서드로 업데이트할 수 있으며, React 재 렌더링을 트리거 하지 않음.
- get메서드로 값을 불러올 수 있음.
- import {useMotionValue} from 'framer-motion';
- useMotionValue(기본값) 설정.
👾 #08_MotionValue값 확인
const x = useMotionValue(0); // 기본값 0으로 설정.
console.log(x);
// x는 set메서드로 업데이트할 수 있으며, React 재 렌더링을 트리거 하지 않음.
// 업데이트 되는 x값을 확인하려면 useEffect(), get()을 사용해야함.
useEffect(()=>{x.onChange(()=>{console.log(x.get())})},[x]);
return (
<Wrapper>
<Box
drag="x"
style={{x}}
dragSnapToOrigin
/>
</Wrapper>
);
👾 #09_버튼 클릭 시 MotionValue값 설정
const x = useMotionValue(0); // 기본값 0으로 설정.
return (
<Wrapper>
<button onClick={()=>{x.set(200)}}>click me</button>
<Box
drag="x"
style={{x}}
dragSnapToOrigin
/>
</Wrapper>
);
👉🏻 버튼 클릭 시 x축으로 200이동
👉🏻 style={{x}}: 스타일에 x값을 연결 시킴!!
- import {useTransform} from 'framer-motion';
- useTransform(MotionValue값, [input값 배열], [output값 배열]) 설정 -> input값(제한값)과 output값의 개수가 같아야함!!
👾 #10_x축으로 -800이동하면 scale이 2(2배 크기), 0이면 scale이 1(원래크기), +800이면 scale이 0이 됨(사라짐).
// -800 -> scale 2, 0 -> scale 0, 800-> scale 0.5
const x = useMotionValue(0); // 기본값 0으로 설정.
const scaleValue = useTransform(x,[-800,0,800],[2,1,0]);
useEffect(()=>{scaleValue.onChange(()=>{console.log(scaleValue.get());})},[scaleValue]); // get()으로 useTransform 인자의 3번째 값이 반환됨.(ex) 0.5690등..)
return (
<Wrapper>
<Box
drag="x"
style={{x, scale: scaleValue}} // useTransform의 변수가 scale일 때 style={{x, scale}}
dragSnapToOrigin
/>
</Wrapper>
);
👾 #11
const x = useMotionValue(0);
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,238,0))"
]
);
return (
<Wrapper style={{ background: gradient}}>
<Box
drag="x"
style={{ x, rotateZ }}
dragSnapToOrigin
dragElastic={1}
/>
</Wrapper>
);
👉🏻 x축으로 드래그 시 배경색 변경됨.
- 뷰포트가 스크롤될 때 업데이트되는 MotionValues를 반환
scrollX— 수평 스크롤 거리(픽셀).
scrollY— 세로 스크롤 거리(픽셀).
scrollXProgress0— 및 사이의 수평 스크롤 진행률 1.
scrollYProgress0— 및 사이의 수직 스크롤 진행률 1.
👾 #12
const Wrapper = styled(motion.div)`
height: 200vh;
width: 100vw;
`;
const Box = styled(motion.div)`
width: 200px;
height: 200px;
`;
...
const {scrollY, scrollYProgress} = useViewportScroll();
const scale = useTransform(scrollYProgress,[0,1],[1,5]);
useEffect(() => {
scrollY.onChange(() => {
console.log(scrollY.get());
console.log(scrollYProgress.get());
})
}, [scrollY, scrollYProgress]);
...
<Wrapper>
<Box style={{ scale }}/>
</Wrapper>
...
👉🏻 y축으로 스크롤을 내리면 Box의 크기가 3배로 커짐
path의 속성을 사용하여 animation 적용
👾 #13_stroke의 length와 fill의 opacity로 animation을 이용
const Svg = styled.svg`
width: 300px;
height: 300px;
path {
stroke: white;
stroke-width: 2;
}
`;
/* const svg = {
start: { pathLength: 0, fill: "rgba(255, 255, 255, 0)" },
end: {
fill: "rgba(255, 255, 255, 1)",
pathLength: 1,
transition: {duration: 3}
},
}; */
...
return (
<Wrapper>
<Svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<motion.path
initial={{ pathLength: 0, fill: "rgba(255, 255, 255, 0)" }}
animate={{ pathLength: 1, fill: "rgba(255, 255, 255, 1)" }}
transition = {{
default:{duration: 5}, // 모든 속성에 적용 됨.
fill: {duration:2, delay:3} // 특정 속성에 지정할 수 있음.
}}
/* variants={svg}
initial="start"
animate="end" */
d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58z" /></Svg>
</Wrapper>
);
👉🏻 pathLength: 현재 위치까지의 path의 길이를 나타냄.
- React js app에서 사라지는 compoenet를 animate함.
- component의 상태가 visible상태여야 함.
<AnimatePresence>조건문</AnimatePresence>
- 3가지 state를 가짐 -> initial, animate, exit
- 컴포넌트가 render되거나 destroy될 때 효과를 줄 수 있음.
👾 #14
const Wrapper = styled(motion.div)`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
`;
const Box = styled(motion.div)`
width: 400px;
height: 200px;
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
position: absolute;
top: 100px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const boxVariants = {
initial: { opacity: 0, scale: 0 },
visible: { opacity: 1, scale: 1, rotateZ: 360 },
leaving: { opacity: 0, scale: 0, y: 50 }
}
function App() {
const [showing, setShwoing] = useState(false);
const toggleHandler = () => { setShwoing(prev => !prev) }
return (
<Wrapper>
<button onClick={toggleHandler}>Click me</button>
<AnimatePresence>
{showing ? (
<Box
variants={boxVariants}
initial="initial"
animate="visible"
exit="leaving"
/>
) : null}
</AnimatePresence>
</Wrapper>
);
}
- custom={0}처럼 사용
- variants를 변경할 수 있음.
- custom프롭을 사용하게 되면 variants는 object를 반환하는 FN으로 변경해야 함.
- 전 요소의 animation이 완전히 끝난 후 다음 animation이 작동 함.
- ex)
<AnimatePresence exitBeforeEnter custom={back}>
- exit가 끝날 때 실행.
- ex)
<AnimatePresence onExitComplete={toggleLeaving}>
// toggleLeaving함수
- 컴포넌트가 처음 mount될 때 initial에 해당하는 animation이 적용되지 않음!!
- ex) `'
👾 #15_slider만들기
const Wrapper = styled(motion.div)`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
`;
const Box = styled(motion.div)`
width: 400px;
height: 200px;
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
position: absolute;
top: 150px;
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);
`;
function App() {
const [visible, setVisible] = useState(1);
const [back, setBack] = useState(false); // 슬라이드가 뒤로가는지 확인하는 state
const nextPlease = () => {
setBack(false);
setVisible(prev => prev === 10 ? 10 : prev + 1)
}
const prevPlease = () => {
setBack(true);
setVisible(prev => prev === 1 ? 1 : prev - 1)
}
const boxVariants = {
// 인수인 isBack은 Box의 custom={back}으로 받은 값임.
entry: (isBack: boolean) => { return { opacity: 0, scale: 0, x: isBack?-500: 500 } },
visible: { opacity: 1, scale: 1, x: 0, transition: { duration: .3 } },
exit: (isBack: boolean) => { return { opacity: 0, scale: 0, x: isBack?500: -500, transition: { duration: .3 } } },
}
return (
<Wrapper>
{/*
AnimatePresence에도 custom 작성,
exitBeforeEnter : 전 요소의 animation이 완전히 끝난 후 다음 animation이 작동 함. ex) <AnimatePresence exitBeforeEnter custom={back},
onExitComplete: exit가 끝날 때 실행. >
*/}
<AnimatePresence custom={back}>
<Box
custom={back} // back에 따라 boxVariants가 달라짐.
key={visible} // React js의 children은 각 고유의 key가 필요하기 때문에 key값이 변하면 다른 요소로 변함.
variants={boxVariants}
initial="entry"
animate="visible"
exit="exit"
>
{visible}
</Box>
</AnimatePresence>
<button onClick={nextPlease} style={{ fontSize: "30px" }}>next</button>
<button onClick={prevPlease} style={{ fontSize: "30px" }}>prev</button>
</Wrapper>
);
}
📍 transition={{type:"tween"}} : linear과 같은 효과로, 슬라이드 될 때 통통튀기는 효과를 없애 줌.
- 이 prop을 element에게 주면 layout이 바뀔 때 알아서 animate됨.
👾 #16
const Box = styled(motion.div)`
width: 400px;
height: 400px;
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const Circle = styled(motion.div)`
background-color: #00a5ff;
height: 100px;
width: 100px;
border-radius: 50px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = ()=>{
setClicked((prev)=> !prev);
}
return (
<Wrapper onClick={toggleClicked}>
<Box style={{justifyContent: clicked ? "center" : "flex-start",alignItems: clicked ? "center" : "flex-start"}}>
<Circle layout /> // layout프롭을 주는것 만으로 알아서 animation을 작동하게 함!!
</Box>
</Wrapper>
);
}
- layoutId=""
- 이 프롭은 Framer에게 여러 요소(UI 컴포넌트)를 하나로 생각하게 만들어 줌.
👾 #17_두 Circle을 연결하기
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = ()=>{
setClicked((prev)=> !prev);
}
return (
<Wrapper onClick={toggleClicked}>
<Box>
{clicked ? <Circle layoutId='circle' style={{borderRadius:0}}/> : null}
</Box>
<Box>
{!clicked ? <Circle layoutId='circle' style={{borderRadius:50}}/> : null}
</Box>
</Wrapper>
);
}
👉🏻 layoutId로 두 Circle을 같은 Circle로 연결해 한개가 일직선으로 움직이는 animation효과를 발생 함.
👾 #18_ 클릭한 박스가 overlay위 정가운데로 이동하는 animation
const Wrapper = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: space-around;
align-items: center;
`;
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 70vw;
gap: 10px;
div:first-child,
div:last-child {
grid-column: span 2;
}
`;
const Box = styled(motion.div)`
background-color: rgba(255, 255, 255, 1);
border-radius: 40px;
height: 200px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const Overlay = styled(motion.div)`
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
`;
function App() {
const [clickedId, setClickedId] = useState<string | null>(null);
return (
<Wrapper>
<Grid>
{["1", "2", "3", "4"].map((n) => (
<Box key={n} layoutId={n} onClick={() => setClickedId(n)} />
))}
</Grid>
<AnimatePresence>
{clickedId ? (
<Overlay
onClick={() => { setClickedId(null) }}
initial={{ backgroundColor: "rgba(0, 0, 0, 0)" }}
animate={{ backgroundColor: "rgba(0, 0, 0, .5)" }}
exit={{ backgroundColor: "rgba(0, 0, 0, 0)" }}>
<Box layoutId={clickedId} style={{ width: 400, height: 200 ,backgroundColor:"rgb(0, 208 , 162)"}} />
</Overlay>
) : null}
</AnimatePresence>
</Wrapper>
);
}
마침 공부중인데 덕분에 감사히 잘 보았습니다.