npm i framer-motion
import { motion } from "framer-motion"
네가 div를 animate하길 원한다면 넌 이렇게 motion.div를 써야함
무언가를 애니메이트하고자 할 때 항상 motion으로부터 HTML element를 불러와야만 함
애니메이트 된 스타일 컴포넌트를 어떻게 가질 수 있을 지 배워볼거임
const Box = styled(motion.div)
``
이렇게 쓸거임
initial안에 네가 쓰길 원하는 초기 상태
spring쓰면 boop하고 팅겨
spring
stiffeness, dmaping
<Box
transition={{ type: "spring", damping: 1 }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
initial로부터 animate되는거야
Variaints: 애니메이션의 stage
start - end, visible - invisible
기본적으로 animation 설정하고, 여기서 우리 Box에게 어디에 variants가 있고 initial variant의 이름이 뭐고, finishing variant의 이름이 뭔지 알려줬지.
const myVars = {
start: { scale: 0 },
end: { scale: 1, rotateZ: 360, transition: { type: "spring", delay: 0.5 } },
};
<Box variants={myVars} initial="start" animate="end" />
설정을 분리된 오브젝트로 옮긴 것
오브젝트 이름은 아무거나 마음대로 해도 되고, 프로퍼티 이름도 마음대로 해도 됨
--
Box를 먼저 나타나게 설정한 다음 자식들이 따라오게 할거임
<Box variants={boxVariants} initial="start" animate="end">
<Circle/>
네가 부모 컴포넌트가 있을 때 그리고 부모 컴포넌트에 initial 이름과 animate 이름이 있고
또 variants가 있을 때 기본값으로 Motion은 이 inital과 animate의 이름인 start와 end를 그 자식들에게 복붙 할거임
Orchestration
delayChildren: 딜레이 시간(초) 후에 하위 애니메이션이 시작
즉 부모 variants 내에서 내가 원하면 모든 자식들에게 delay를 할 수 있음
staggerChildren: 하위 컴포넌트의 애니메이션에 지속 시간(초)만큼 시차 줄 수 있음
staggerChildren:0.5
자동적으로 Motion은 첫 번째 원에 0.5s delay를 줄거고 그 다음 원에서 0.5s delay 이런식 ~~
Hover: hover 제스처는 포인터가 컴포넌트 위로 이동하거나 컴포넌트를 떠날 때를 감지
whileHover: VariantLabels | TargetAndTransition
호버 제스처가 인식되는 동안 애니메이션할 속성 또는 변형 레이블
Tap
whileTap: VariantLabels | TargetAndTransition
컴포넌트를 누르고 있는 동안 애니메이션할 속성 또는 변형 레이블
Drag
drag: boolean | "x" | "y"
이 요소에 대해 끌기를 활성화
기본적으로 false로 설정! 양방향으로 드래그하려면 true로 설정!
특정 방향으로만 드래그하려면 "x" 또는 "y"를 설정
whileDrag: VariantLabels | TargetAndTransition
드래그 제스처가 인식되는 동안 애니메이션할 속성 또는 변형 레이블
const boxVariants = {
hover: { scale: 1.5, rotateZ: 90 },
click: { scale: 1, borderRadius: "100px" },
drag: { backgroundColor: "rgb(46,204,113)", transition: { duration: 10 } },
};
<Box
drag
variants={boxVariants}
whileHover="hover"
whileDrag="drag"
whileTap="click"
/>
숫자들 넣을 때 rgb나 rgba를 넣어야함!
dragConstraints
허용된 드래그 가능 영역에 제약 조건을 적용
dragConstraints 에는 드래그 가능한 컴포넌트의 가장자리 거리를 정의
ref: 네 코드로 특정 element를 잡을 수 있는 방법
dragSnapToOrigin: boolean
true인 경우 드래그 가능한 요소는 드래그를 놓을 때, 원점으로 다시 애니메이션
dragElastic={0.5}
기본값이 0.5
elastic은 기본적으로 당기는 힘 같은게 있다는 의미!
import styled from "styled-components";
import { motion } from "framer-motion";
import { useRef } from "react";
const Wrapper = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
`;
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 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), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const boxVariants = {
hover: { rotateZ: 90 },
click: { borderRadius: "100px" },
};
function App() {
const biggerBoxRef = useRef<HTMLDivElement>(null);
return (
<Wrapper>
<BiggerBox ref={biggerBoxRef}>
<Box
drag
dragSnapToOrigin
dragElastic={0.5}
dragConstraints={biggerBoxRef}
variants={boxVariants}
whileHover="hover"
whileTap="click"
/>
</BiggerBox>
</Wrapper>
);
}
export default App;
MotionValue: 애니메이션 내의 수치를 트래킹(추적)할 때 필요
MotionValue가 업데이트 될 때 렌더링 싸이클을 발동시키지 않을거임
=> MotionValue가 ReactJs State로 살지 않는다는거임. State가 아님. 움직이는 매순간 재랜더링 되지 않음
모든 모션 컴포넌트는 내부적으로 MotionValues를 사용하여 애니메이션 값의 상태와 속도를 추적. 일반적으로 이들은 자동으로 생성됨.
const x = useMotionValue(0);
useEffect(() => {
x.onChange(() => console.log(x.get()));
}, [x]);
보면 알 수 있음
import { motion, useMotionValue } from "framer-motion"
export function MyComponent() {
const x = useMotionValue(0)
return < motion.div style={{ x }} />
}
const x = useMotionValue(0)
useMotionValue 후크로 MotionValues를 생성
useMotionValue에 전달된 값은 MotionValue의 초기 상태로 작동
x.set(100)
set 메서드로 업데이트 가능
이것은 React 리렌더링을 트리거하지 않음
x.get() // 100
MotionValue는 문자열이나 숫자가 될 수 있음
get 메소드로 값읽기 가능
Values은 한 숫자를 다른 숫자로 변하는 걸 가능하게 해줌
useTransform(value:MotionValue,input, output)
useTransform(x,[-800,0,800], [2,1,0.1])
배열을 넣을거야 - 세 개의 값들을 원한다는 걸 알리는거
x가 -800일 때, x가 0일 때, x가 800일 때,, 일케!
x가 -800일 때 2를 얻고 싶은거고, x가 0일 때 1을 얻고 싶은거고, x가 800일 때 0.1을 얻고 싶은거
입력값들과 출력값들의 갯수는 같아야지 +_+
const x = useMotionValue(0)
const input = [-200, 0, 200]
const output = [0, 1, 0]
const opacity = useTransform(x, input, output)
return < motion.div drag="x" style={{ x, opacity }} />
function App() {
const x = useMotionValue(0);
const scale = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);
return (
<Wrapper>
<Box style={{ x, scale }} drag="x" dragSnapToOrigin />
</Wrapper>
);
}
색깔 바꾸기
const gradient = useTransform(
x,
[-800, 800],
[
"linear-gradient(135deg, rgb(0, 210, 238), rgb(0, 83, 238))",
"linear-gradient(135deg, rgb(0, 238, 155), rgb(238, 178, 0))",
]
);
scrollX: 실제 수평 스크롤 픽셀 ex) 500px
scrollY: 실제 수직 스크롤 픽셀 ex) 500px
scrollXProgress : 0 ~ 1 사이의 수평 스크롤
scrollYProgress : 0 ~ 1 사이의 수직 스크롤(가장 상단 0, 가장 하단 1)
const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
Fontawesome Airbnb Logo
< />모양 클릭해서 svg복사 후 사용하
링크텍스트
Line drawing
svg 엘리먼트에 'pathLength', 'pathSpacing', 'pathOffset' 속성을 사용하여 Line drawing 애니메이션을 만들 수ㅇㅇ
pathLength: 현재 우리 위치까지의 path의 길이를 나타냄
import styled from "styled-components";
import { motion } from "framer-motion";
const Wrapper = styled(motion.div)`
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
`;
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,
},
};
function App() {
return (
<Wrapper>
<Svg
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<motion.path
variants={svg}
initial="start"
animate="end"
transition={{
default: { duration: 5 }, //모든 property에 적용되는 값
fill: { duration: 1, delay: 3 },
}}
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"
></motion.path>
</Svg>
</Wrapper>
);
}
export default App;
transition prop으로 각각 다른 방법으로 animation 설정 가능
const svg={start:{pathLength:0, fill:"rgba(255,255,255,0)"}, end:{
fill:"rgba(255,255,255,1)", pathLength:1, transition:{duration:5}},};
만약 일케 쓰면 pathLengh나 fill이나 똑같은 DURATION 가지게 됨
react js app에서 사라지는 component를 animate함
AnimatePresence의 딱 한가지 규칙은 visible 상태여야 한다는거임
밖에다가 AnimatePresence를 써야함
안쪽에 나타나거나 사라지는 것이 있따면 그것을 animate할 수 있도록 해줌
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, setShowing] = useState(false);
const toggleShowing = () => setShowing((prev) => !prev);
return (
<Wrapper>
<button onClick={toggleShowing}>Click</button>
<AnimatePresence>
{showing ? (
<Box
variants={boxVariants}
initial="initial"
animate="visible"
exit="leaving"
/>
) : null}
</AnimatePresence>
</Wrapper>
);
}
next를 누르면
const [visible, setVisible] = useState(1);
const nextPlease = () => setVisible((prev) => (prev === 10 ? 10 : prev + 1));
return (
<Wrapper>
<AnimatePresence>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) =>
i === visible ? (
<Box
variants={box}
initial="invisible"
animate="visible"
exit="exit"
key={i}
>
{i}
</Box>
) : null
)}
</AnimatePresence>
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
</Wrapper>
);
useState(2)가 될거임
이게 2가 되면 이전 Box가 더이상 visible이 아니게 되고 여기 AnimatePresence가 쓰였기 때문에 exit을 실행시키게 될거야
즉 버튼을 클릭하면 index가 수정되고 수정되면 첫번째 box가 사라짐
box가 사라지면 AnimatePresence안에 있기 때문에 exit을 실행하게 될거임
또한 index가 수정되었기 때문에 두번째 box가 나타날거임
두번째 Box가 나타나면 initial과 animate의 variant가 실행될거임
React js가 이전 component를 삭제하고 새 것을 보여주는 곳에는
initial, animate,exit 이 세가지의 animation이 모두 실행됨
만약 component의 key가 바뀌면 새로운걸 re-render할거임
왜냐하면 모든 key는 고유해야하기 때문임
만약 key를 바꾸면 이전 component는 없어지고 이전 component가 없어지면 AnimatePresence가 exit animation을 실행함
custon: variants에 데이터를 보낼 수 있게 해주는 property
custom을 사용할거면 variant를 object를 return하는 function으로 바꿔야 함.
이 funtion은 한 argument를 가지고 그 argument는 custom prop에서 옴
entry: (isBack: boolean) => ({
x: isBack ? -500 : 500,
opacity: 0,
scale: 0,
}),
custom={back}
back의 기본값이 false니까 isBack은 false가 됨
exitBeforeEnter
true로 설정하면 AnimatePresence는 한 번에 하나의 컴포넌트만 랜더링
1번의 exit이 완전히 실행되었을 때 2번 box가 올 수 있음
< AnimatePresence exitBeforeEnter>
< motion.div key={currentItem} exit={{ opacity: 0 }} />
< /AnimatePresence>
layout prop
이 prop을 element에게 주면 그 element의 layout이 바뀔 때 알아서 animate가 됨
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked(prev) => !prev);
return (
<wrapper onClick={toggleClicked}>
<Box
sytle = {{justifyContent: clicked ? "center" : "flex-start",
alignItems:clicked ? "center":"flex-start",}}
>
<Circle layout />
<Box>
</Wrapper>
);
Farmer Motion은ㅇ 무언가 외부의 힘에 의해 바뀐 것을 감지함
을 통해서 css의 변화는 자동으로 animate 될거임
shared layout
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Box>
{!clicked ? (
<Circle layoutId="circle" style={{ borderRadius: 50 }} />
) : null}
</Box>
<Box>
{clicked ? (
<Circle layoutId="circle" style={{ borderRadius: 0, scale: 2 }} />
) : null}
</Box>
</Wrapper>
);
}
다른 Circle component임 근데 layoutId를 통해서 두개를 연결하게 해줌 그리고 이 둘이 같다고 생각함
Layout 애니메이션 다양한 사용 예시들
https://www.framer.com/docs/animate-shared-layout/#syncing-layout-animations
layoutId는 한 component를 다른 component와 연결할 수 있도록 해줘
layoutId도 n이라고 할거임
layoutId는 string이어야 하기 때무넹 두가지 옵션이 있음
layoutId= {n+""} 하거나
array안에 있는 것들을 string으로 바꿔주던지
["1", "2", "3", "4"]
<Box onClick={() => setId(n)} key={n} layoutId={n} />
=> 이걸 써야 이 funtion은 이게 클릭될때만 실행될거임
안쓰면 바로 시작됨
클릭하면 요 funtion이 실행될거고 그게 우리가 써 준 setId(n)을 실행시킬거임
여기서 하고 있는 것은 n에 따라 state를 변경하고 있는거임
즉 클릭할 때마다 state 변경
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react";
import styled from "styled-components";
const Wrapper = styled.div`
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
height: 100vh;
width: 100vw;
box-sizing: border-box;
background: linear-gradient(135deg, #e09, #d0e);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: relative;
padding: 0 50px;
`;
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 300px 300px;
gap: 15px;
max-width: 890px;
width: 100%;
`;
const Box = styled(motion.div)`
background-color: rgba(255, 255, 255, 0.6);
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
`;
const Circle = styled(motion.div)`
background-color: white;
width: 80px;
height: 80px;
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4), 0 0px 5px rgba(0, 0, 0, 0.1);
`;
const Button = styled(motion.span)`
cursor: pointer;
padding: 10px;
margin-top: 50px;
border-radius: 5px;
color: white;
background-color: white;
font-weight: 600;
font-size: 18px;
position: absolute;
bottom: 75px;
`;
const Overlay = styled(motion.div)`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
`;
const BigBox = styled(motion.div)`
background-color: white;
width: 400px;
height: 300px;
border-radius: 10px;
`;
const buttonVariants = {
zero: {
color: "#0052D4",
transform: "scale(1)"
},
one: {
color: "#ff6a00",
transform: "scale(1.2)"
}
};
const boxVariants = {
initial: (i) => ({
scale: 1,
x: 0,
y: 0
}),
hover: (i) => ({
scale: 1.1,
y: i ? -15 : 15,
x: i ? -20 : 20
})
};
const overlayVariants = {
hidden: {
backgroundColor: "rgba(0, 0, 0, 0.0)"
},
visible: {
backgroundColor: "rgba(0, 0, 0, 0.5)"
}
};
export default function App() {
const [index, setIndex] = useState(0);
const [selectedBox, setSelectedBox] = useState(null);
const onToggleClick = () => {
setIndex((prev) => (prev === 1 ? 0 : 1));
};
const onClickBox = (i) => {
setSelectedBox(i);
};
return (
<Wrapper>
<Grid>
<Box
onClick={() => onClickBox("1")}
layoutId="1"
variants={boxVariants}
custom={1}
initial="initial"
whileHover="hover"
/>
<Box>{index === 0 ? <Circle layoutId="circle" /> : null}</Box>
<Box>{index === 1 ? <Circle layoutId="circle" /> : null}</Box>
<Box
onClick={() => onClickBox("0")}
variants={boxVariants}
custom={0}
layoutId="0"
initial="initial"
whileHover="hover"
/>
</Grid>
<Button
animate={index === 1 ? "one" : "zero"}
variants={buttonVariants}
initial="zero"
onClick={onToggleClick}
>
Switch
</Button>
<AnimatePresence>
{selectedBox ? (
<Overlay
variants={overlayVariants}
animate="visible"
exit="hidden"
onClick={() => onClickBox(null)}
>
<BigBox layoutId={selectedBox}></BigBox>
</Overlay>
) : (
""
)}
</AnimatePresence>
</Wrapper>
);
}
Circle 컴포넌트의 이동 motion.
토글 버튼을 생성해줍니다.
Boolean state를 하나 생성해줍니다. 해당 state는 true일 경우, 두 번째 컴포넌트의 Circle을, false일 경우 세 번째 컴포넌트의 Circle을 렌더링해야 합니다.
State의 값에 따라 조건적인 렌더링을 위해, 아래와 같이 Box의 컴포넌트 내부에 삼항연산자를 적용해주도록 합시다.
<Box>{index === 0 ? <Circle layoutId="circle" /> : null}</Box>
<Box>{index === 1 ? <Circle layoutId="circle" /> : null}</Box>
Hover된 컴포넌트의 크기가 커지는 motion.
해당 기능을 구현하기 위해 custom 프로퍼티를 사용하여, variants 내부에서 조건에 따라 서로 다른 animation을 적용해보도록 하겠습니다.
위의 코드에 대해 살펴보겠습니다. 위 코드는 1에서 4의 id가 담겨있는 Array에 map을 사용해 컴포넌트를 렌더링합니다. 이때, 각 컴포넌트는 hover의 motion을 부여하기 위한 hoverVariants(하단 사진 코드)를 가지며, whileHover일 때 variants의 hover상태를 적용해줍니다. Transition은 type은 linear을 적용해주었습니다. 가장 중요한 custom입니다. whileHover 상태일 때, 적용되는 hover은 아래와 같이 조건부로 적용되게 해두었습니다. 따라서 custom의 값이 무엇이냐에 따라 적용되는 animation(motion)에 차이가 발생합니다.
요컨대 1,2,3,4의 Array에 map을 사용해 렌더링한 각 컴포넌트는 각각 1,2,3,4의 custom 값을 가진 컴포넌트가 됩니다. 따라서 whileHover 상태일 때, 적용되는 hoverVar은 hover될 경우 scale이 1.2가 되며, 1,3번째 박스(왼쪽 박스)일 경우 x로 -25. 2,4번째 박스(오른쪽 박스)일 경우 x로 25 이동합니다. 또한, 1,2번째 박스(위쪽 박스)일 경우 y로 -15. 3,4번째 박스(아래 박스)일 경우 y로 15로 이동하는 코드입니다.
선택된 Box 컴포넌트를 중앙으로 이동하며 Overlay가 깔리는 motion.
컨셉은 다음과 같습니다. Box를 클릭했을 때, Overlay의 z-index를 최우선순위에 두며, Overlay 컴포넌트 내부에 Box 컴포넌트 하나를 렌더링합니다. 이때, 클릭한 컴포넌트와 Overlay 내부의 Box 컴포넌트가 동일한 Box인 것처럼 보여주기 위해, 두 컴포넌트에 동일한 layoutId를 부여해주어야 합니다.
필자 이를 구현하기 위해 다음과 같은 방법을 사용했습니다. 우선 State를 하나 생성합니다.
const [id, setId] = React.useState(null)
우리는 Box 컴포넌트를 클릭할 때, setId를 통해 클릭 된 컴포넌트의 id를 새로운 id 값으로 부여할 것입니다. 그리고 아래의 삼항연산자를 통해 Overlay를 렌더링합니다.
Overlay 내부에 선언된 overlayHandler는 한 가지 조건만 만족하면 됩니다. Overlay 컴포넌트를 클릭했을 때, setId라는 modifier를 통해 다시 id를 null 값으로 돌려놓을 것.