
AnimateSharedLayout | Framer for Developers
AnimateSharedLayout | Framer for Developers
Animate layout changes across, and between, multiple components.
www.framer.com
https://github.com/framer/motion
GitHub - framer/motion: Open source, production-ready animation and gesture library for React
Open source, production-ready animation and gesture library for React - GitHub - framer/motion: Open source, production-ready animation and gesture library for React
github.com
애니메이션을 쉽게 사용하게 해주는 라이브러리이며 디자이너가 개발할 때 활용하는 Framer라는 곳에서 개발하였습니다.
기본문법
import React from 'react';
import styled from "styled-components"
import {GlobalStyle} from "./styles/global-styles"
import {motion } from "framer-motion"
function App() {
return (
<>
<GlobalStyle/>
<Wrapper>
<Box
transition={{delay:0.5, type:"spring", }}
initial={{scale:0}}
animate={{scale:1, rotateZ:360}}/>
</Wrapper>
</>
);
}
const Wrapper = styled.div`
width: 100vw;
height: 100vh;
background-color: black;
display: flex;
justify-content: center;
align-items: center;
`
const Box = styled(motion.div)` // Framer-motion과 styled컴포넌트를 사용하는 방법
width: 200px;
height: 200px;
background-color: red;
border-radius: 15px;
box-shadow: 0 2px 3px rgba(0,0,0,0.1), 0 10px 20px rgba(0,0,0,0.6);
`
export default App;
기본적으로 <motion.div animate={{ x: 0 }} /> 이러한 구조를 가지고 있는데
제가 세팅한 환경에서 styled-components와 같이 쓰기 위해서는 단순히 motion.컴포넌트명 으로 사용할 순 없습니다.
그래서 이것을 사용하기 위해서는 기존에 컴포넌트로 선언하는 부분에서 motion 을 사용할 것을 명시해야 합니다.
const Box = styled(motion.div)` // Framer-motion과 styled컴포넌트를 사용하는 방법
...
`
const Box = styled.div` //motion.div로 쓰지 않으면 사용할 수 없다
...
`
기본적인 props들은 아래 공식 문서에서 활용할 수 있습니다.
Documentation | Framer for Developers
An open source, production-ready motion library for React on the web.
www.framer.com
Variants
variants는 기본적으로 코드를 깔끔하게 하기위해 Object로 묶어 변수형식을 사용할 수 있습니다
- 기본형식
<Box
transition={{delay:0.5, type:"spring", }}
initial={{scale:0}}
animate={{scale:1, rotateZ:360}}
/>
- variants
const BoxVars = {
start : {
opacity:0,
scale:0.5
},
end : {
scale:1,
opacity:1,
transition: {
type:"spring",
delay:0.5,
}
}
}
<Box variants={BoxVars} initial="start" animate="end">
훨씬 더 간결해지고 재사용 할 수 있는 코드로 바뀌었습니다.
또한 variants의 장점은 이걸로 끝나지 않는데 위와 같이 Box에서 자식요소가 있을 때 코드를 훨씬 더 간결하게 표현할 수 있게 됩니다.
const BoxVars = {
start : {
opacity:0,
scale:0.5
},
end : {
scale:1,
opacity:1,
transition: {
type:"spring",
delay:0.5,
duration:0.5,
bounce:0.5,
delayChildren:1,
staggerChildren:0.5
}
}
}
const CircleVars = {
start : {
opacity:0,
y:10
},
end: {
opacity:1,
y:0,
},
}
<Box variants={BoxVars} initial="start" animate="end">
<Circle variants={CircleVars}/>
<Circle variants={CircleVars}/>
<Circle variants={CircleVars}/>
<Circle variants={CircleVars}/>
</Box>
위 코드에서 기본적으로 부모요소에 variants가 있을 때 Framer-Motion은 default값을 initial과 animate의 이름을 자식에게 복사해서 붙여넣어줍니다
<Circle initial="start" animate="end"/> 이런 구조를 가지게 되며 자식요소에서 variants를 쓸 때 intial="start"와 animate="end"를 또 적어서 전달해줄 필요는 없게 되지만 위 선언한 CircleVars 에서의 Object 키 값은 일치해야 합니다.( 위에선 “start”와 “end”로 작성함)
이처럼 자식요소를 감지하는 기능 때문에 부모요소에서 자식에게 명령을 내릴 수 있는데 delayChildren , staggerChildren 이 있습니다.
delayChildren : 말 그대로 자식요소(전체)에게 delay를 적용시키는 것입니다.
staggerChildren : 자식요소(각각 개별)에 각 순서에 따라 지연시간을 적용시킵니다
ex) 첫 번째 <Circle/> 에게 0.5.. 두 번째 <Circle/> 에서 0.5 * 2... 순차적으로 적용시켜주는 명령어 입니다.
이렇게 variants는 자식요소를 갖게 되었을 때 더 간결하고 쉽게 사용할 수 있으며 동작은
https://www.framer.com/motion/ 에서 Examples를 통해서 확인할 수 있습니다.
Production-Ready Animation Library for React | Framer Motion
Framer Motion is a production-ready React animation and gesture library.
www.framer.com
Drag
Drag는 말그대로 Drag할 수 있게 만들어줍니다
<Box drag/> //그냥 drag만 붙이면 사용가능
<Box drag="x"/> //x축으로 drag가능
<Box drag="y"/> //y축으로 drag가능
- Drag를 사용한 하나의 예시
const constraintsRef = useRef<HTMLDivElement>(null)
return (
<Wrapper ref={constraintsRef}>
<Box
drag
dragConstraints={constraintsRef}
/>
</Wrapper>
)
const Wrapper = styled(motion.div)`
width: 600px;
height: 500px;
background-color: rgba(255,255,255,0.2);
border-radius: 40px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
`
const Box = styled(motion.div)` // Framer-motion과 styled컴포넌트를 사용하는 방법
width: 200px;
height: 200px;
background-color: rgba(255,255,255,0.2);
border-radius: 40px;
/* display: grid;
grid-template-columns: repeat(2, 1fr); */
box-shadow: 0 2px 3px rgba(0,0,0,0.1), 0 10px 20px rgba(0,0,0,0.6);
`
<Box
drag
dragSnapToOrigin
dragElastic={0}
dragConstraints={biggerBoxRef}
variants={BoxVariants}
whileHover={"hover"}
whileDrag={"drag"}
whileTap={"click"}
/>
dragSnapToOrigin :원 상태로 복귀
dragElastic:탄성.. 마우스로 끌어당기는게 무거워진다
dragConstraints: 부모에 크기만큼 drag를 할 수 있게 만들어준다(부모를 useRef로 지정해야 함)
whileHover : hover했을 때
whileDrag :drag중일 때
whileTap : 클릭했을 때
→ 이것들은 그냥 참고로만 알아두자 공식문서에 다 나와있다.
useMotionValue, useTransform
const xDrag = useMotionValue(0); // ex) xDrag는 x값을 받아오기 위한 MotionValue이다
<Box style={{ x:xDrag, rotateZ, scale}} drag="x" dragSnapToOrigin/>
기본적으로 useMotionValue는 hooks처럼 상태가 변경되었을 때 리렌더링 되지 않는다.
값을 받아오더라도 갱신이 되지 않는다고 이해하면 된다. 만약 값(애니메이션에 대한)을 받아올 때 갱신이 발생한다면 상당한 자원낭비를 할 수 있게 되므로 갱신되지 않아야 한다.
(React.memo)
위 코드에서는 x축으로 drag될 때 x축 위치를 받아오는 xDrag변수가 있다.
이것을 사용하기 위해서 useTransform을 사용함
const rotateZ = useTransform(xDrag, [-800, 800], [-360, 360]);
useTransForm은 useMotionValue에서 값을 받아온 뒤 입력한 범위값에 따라 그에 맞는 범위의 출력값을 얻게 해준다
- (한가지 값)을 받아와서 (우리가 확인해줬으면 하는 입력값 범위에 따라서) (그에 맞는 범위의 출력값)을 얻을 수 있게 해준다
const xDrag = useMotionValue(0); //이 x는 x값을 받아오기 위한 MotionValue이다
const rotateZ = useTransform(xDrag, [-800, 800], [-360, 360]);
<Box style={{ x:xDrag, rotateZ}} drag="x" dragSnapToOrigin/>
현재 x값을 xDrag에 받아온 뒤 useTransform에서 필요한 3가지 값들인 확인해줬으면 하는 값, 그 값의 범위 , 그에 맞는 출력값을 입력하면 된다. (입력값 범위와 출력값의 범위 갯수는 같아야한다, 배열의 원소 갯수)
위 코드를 해석해보면 x축에서 xDrag가 -800, 800 값의 범위만큼 움직일 때 -360부터 360까지의 값으로 바꿔서 출력해달라는 것과 같고 이것을 rotateZ로 사용하였으니 Box를 x축으로 입력한 범위 만큼 drag할 때마다 출력값만큼 회전, 역회전한다는 것을 알 수 있다
말로 풀어쓰니 상당히 어렵다..
아무튼 useTransform은 어떠한 값을 받아서 움직일 때 지정한 입력범위만큼의 값을 출력범위로 나눠 출력시켜 준다.
useViewportScroll
const {scrollY, scrollYProgress} = useViewportScroll();
const scale = useTransform(scrollYProgress, [0, 1], [1,5])
<Box style={{ scale }}/>
useViewportScroll() 은 스크롤 값을 반환해 주는데 scrollY는 픽셀단위, progress는 퍼센트로 반환을 해준다.
위 코드는 스크롤에 따라 값이 커지고 작아지는 효과를 가진다.
Path
Framer-motion에 있는 Path는 svg이미지에 애니메이션을 추가해준다
import styled from 'styled-components'
import { motion, useMotionValue, useTransform, useViewportScroll } from "framer-motion"
const Svg = styled.svg`
width: 300px;
height: 100px;
path {
stroke:white;
stroke-Width:5;
}
`
const svg = {
start: {
pathLength:0, fill: "rgba(255,255,255,0)"
},
end : {
pathLength:1,
fill: "rgba(255,255,255,1)",
transition:{
default: {duration:5},
fill:{duration:2, delay:3}
}
}
}
function SVG() {
return (
)
}
export default SVG
기본적으로 https://fontawesome.com/ 에 가서 개발자도구를 열어보면 svg이미지를 가져올 수 있다.
그리고 styled-components로 svg에 CSS를 적용할 수 있고 애니메이션을 구현하기 위해
<svg></svg> 안에 path.motion 으로 만들어 주어야 한다. 그리고 path에 animation을 적용시키면 된다
path : 2가지 CSS를 적용시켰는데 path 자체에 fill을 넣는다면 내부 색깔이 바뀐다.
stroke,stroke-Width : 적용시킨다면 border 효과처럼 내부의 라인들에 색깔과 굵기가 바뀌게 된다
pathLength : svg이미지의 0~1의 비율을 가지고 svg 전체에 일정비율로 채우는 효과를 가진다
//variants
const svg = {
start: {
pathLength:0, fill: "rgba(255,255,255,0)"
},
end : {
pathLength:1,
fill: "rgba(255,255,255,1)",
transition:{
default: {duration:5},
fill:{duration:2, delay:3}
}
}
}
transition효과를 줄 때 바로 duration 같은 option값을 주게 된다면 전체에 적용되는 default로 적용이 된다. 하지만 transition에 default를 준다면 각 animation Effect 마다 각각의 transition 옵션 값들을 줄 수 가 있다
AnimatePresence
컴포넌트가 사라질 때 애니메이션 동작을 지시하는 Framer-motion에 기능이다
import {useState} from 'react'
import styled from 'styled-components';
import {motion, AnimatePresence} from "framer-motion"
//AnimatePresence : 컴포넌트가 사라질 때 애니메이션 동작을 지시
const Box = styled(motion.div)` // Framer-motion과 styled컴포넌트를 사용하는 방법
width: 200px;
height: 200px;
background-color: rgba(255,255,255,0.2);
border-radius: 40px;
/* display: grid;
grid-template-columns: repeat(2, 1fr); */
box-shadow: 0 2px 3px rgba(0,0,0,0.1), 0 10px 20px rgba(0,0,0,0.6);
position: absolute;
top:10px;
`
const BoxVariants = {
//AnimatePresence variants는 조금다르다
initial : {
opacitiy:0,
scale:0,
},
visiable : {
opacity:1,
scale:1,
rotateZ:360,
},
exit : {
opacity:0,
scale:0,
y:100,
}
}
function AnimatePresenceEx() {
const [showing, setShowing] = useState(false);
const toggleShowing = () => setShowing((prev) => !prev)
return (
//AnimatePresence의 한가지 규칙은 visible 상태여야 한다.
//children으로 조건문이 있어야 한다
<>
<AnimatePresence>
{showing ? <Box variants={BoxVariants} initial="initial" animate="visiable" exit="exit"/> : null}
</AnimatePresence>
{/* {showing ? <AnimatePresence><Box/></AnimatePresence> : null}
--> 이렇게 만들면 안된다. 보여야함
왜냐하면 </AnimatePresence>는 안쪽에 사라지는 것을 감지하고 그것을 animate시켜준다
*/}
<button onClick={toggleShowing}>Click??</button>
</>
)
}
export default AnimatePresenceEx
AnimatePresence의 한가지 규칙은 visible 상태여야 한다. 왜냐하면 내부에서 사라지는 Component를 감지하여 animation을 적용시켜주기 때문에 사라지는 Component에 붙어서는 안된다.
<AnimatePresence>
{showing ? <Box variants={BoxVariants} initial="initial" animate="visiable" exit="exit"/> : null}
</AnimatePresence>
//이러한 구조가 필요하다
{showing ? <AnimatePresence><Box/></AnimatePresence> : null} // 불가능
두 번째로는 children으로 조건문이 있어야 한다. 위 이유처럼 사라지는 컴포넌트를 감지하고 적용시켜줄 예정이기 때문에 필요하다.
const BoxVariants = {
//AnimatePresence variants는 조금다르다
initial : {
opacitiy:0,
scale:0,
},
animate: {
opacity:1,
scale:1,
rotateZ:360,
},
exit : {
opacity:0,
scale:0,
y:100,
}
}
마지막으로 계속 사용했던 variants는 initial 과 animate 에 필요한 object 였었는데 하나가 더 있다.
<Box variants={BoxVariants} initial="initial" animate="animate" exit="exit"/>
exit으로 component가 종료될 때 실행되는 효과를 가진다
그래서 현재 목차 맨 위 코드를 보면 버튼을 누르면 사라졌다 나오는 Effect를 가지는데 여기서 initial , animate , exit 효과를 거치며 동작한다고 볼 수 있다.
import {useState} from 'react'
import styled from 'styled-components';
import {motion, AnimatePresence} from "framer-motion"
//AnimatePresence : 컴포넌트가 사라질 때 애니메이션 동작을 지시
const Box = styled(motion.div)` // Framer-motion과 styled컴포넌트를 사용하는 방법
width: 200px;
height: 200px;
background-color: rgba(255,255,255,0.2);
border-radius: 40px;
position: absolute;
top:100px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
`
const BoxVariants = {
//AnimatePresence variants는 조금다르다
initial : {
opacitiy:0,
scale:0,
},
animate : {
opacity:1,
scale:1,
rotateZ:360,
},
exit : {
opacity:0,
scale:0,
y:100,
}
}
const BoxSlide = { // 이 Box는 slide를 만들기 위한 Box
entry : (back:boolean) => ({
x: back ? -500 : 500,
opacity:0,
scale:0
}),
center : {
x:0,
opacity:1,
scale:1,
transition: {
duration:0.3
}
},
exit : (back:boolean) => ({
x: back ? 500 : -500,
opacity:0,
scale:0,
transition: {
duration:0.3
}
})
}
function AnimatePresenceEx() {
const [showing, setShowing] = useState(false);
const toggleShowing = () => setShowing((prev) => !prev)
const [visible, setVisible] = useState(1);
const [back, setBack] = useState(false);
const nextPlease = () => {
setBack(false)
setVisible(prev=> prev === 10 ? 1: prev+1)
};
const prevPlease = () => {
setBack(true)
setVisible(prev=> prev === 1 ? 10 : prev-1)
}
return (
//AnimatePresence의 한가지 규칙은 visible 상태여야 한다.
//children으로 조건문이 있어야 한다
<>
<AnimatePresence exitBeforeEnter custom={back}>
<Box custom={back} variants={BoxSlide} initial="entry" animate="center" exit={"exit"} key={visible}>{visible}</Box>
</AnimatePresence>
{/* {showing ? <AnimatePresence><Box/></AnimatePresence> : null}
--> 이렇게 만들면 안된다. 보여야함
왜냐하면 </AnimatePresence>는 안쪽에 사라지는 것을 감지하고 그것을 animate시켜준다
*/}
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
</>
)
}
export default AnimatePresenceEx
next, prev를 누르면 넘어가는 간단한 슬라이드가 있는데 이것을 보면 이전에 작성했던 initial , animate ,exit 순서를 따라가게 되는걸 확인할 수 있다.
const [visible, setVisible] = useState(1);
const nextPlease = () => {
setBack(false)
setVisible(prev=> prev === 10 ? 1: prev+1)
};
const prevPlease = () => {
setBack(true)
setVisible(prev=> prev === 1 ? 10 : prev-1)
}
const BoxSlide = { // 이 Box는 slide를 만들기 위한 Box
entry : {
x: back ? -500 : 500,
opacity:0,
scale:0
},
center : {
x:0,
opacity:1,
scale:1,
transition: {
duration:0.3
}
},
exit : {
x: back ? 500 : -500,
opacity:0,
scale:0,
transition: {
duration:0.3
}
}
}
<AnimatePresence >
<Box variants={BoxSlide} initial="entry" animate="center" exit={"exit"} key={visible}>{visible}</Box>
</AnimatePresence >
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
하지만 문제가 있는데 next와 prev가 날아오는 방향이 같아진다.
이게 무슨말이냐면 슬라이드 next를 누르면 오른쪽에서 왼쪽으로 넘어온다고 할 때, prev를 누르면 왼쪽에서 오른쪽으로 넘어와야 한다. 하지만 여기서는 구분이 없다보니 같은 방향으로 날라오게 되고 상당히 어색하다는 것을 알 수 있다.
이럴 때 우리는 조건을 걸어서 구분을 하게 되는데, 그럴 때 사용하는 게 custom 이다.
const [back, setBack] = useState(false);
<AnimatePresence custom={back}>
<Box custom={back} variants={BoxSlide} initial="entry" animate="center" exit={"exit"} key={visible}>{visible}</Box>
</AnimatePresence >
custom은 AnimatePresence 에도 적어줘야 하고 Box에도 전달할 props를 적어줘야 한다. 여기서는 뒤로 움직이는 것을 확인할 수 있게 back이라는 변수를 사용하였다.
그 다음 variants로 가서 각각의 initial , animate ,exit 들이 object를 반환하는 함수로 만들어 줘야 한다
const BoxSlide = { // 이 Box는 slide를 만들기 위한 Box
entry : (back:boolean) => ({
x: back ? -500 : 500,
opacity:0,
scale:0
}),
center : {
x:0,
opacity:1,
scale:1,
transition: {
duration:0.3
}
},
exit : (back:boolean) => ({
x: back ? 500 : -500,
opacity:0,
scale:0,
transition: {
duration:0.3
}
})
}
entry : (back:boolean) => {
return {
x: back ? -500 : 500,
opacity:0,
scale:0
}
},
// 바로 괄호로 감싸주게 되면 return을 적지않아도 된다!
entry : (back:boolean) => ({
x: back ? -500 : 500,
opacity:0,
scale:0
}),
Object를 반환하는 각각의 variants 들을 설정했다면 custom props를 전달받아 variants 내부에서 사용할 수 있게 된다!
다수의 props들을 전달받을 수 있는지 확인해보고 싶다 ⇒ 배열로 전달하게 되면 multiprops를 쓸 수 있다
https://github.com/framer/motion
GitHub - framer/motion: Open source, production-ready animation and gesture library for React
Open source, production-ready animation and gesture library for React - GitHub - framer/motion: Open source, production-ready animation and gesture library for React
github.com
'TIL > 개념정리' 카테고리의 다른 글
swagger가 필요한 이유와 정리 (0) | 2022.07.19 |
---|---|
API 쉽게 이해하기 (0) | 2022.07.19 |
@keyframes - animation 사용법 (0) | 2022.07.14 |
styled-components 문법 정리 (0) | 2022.07.13 |
File upload 취약점 (0) | 2022.07.12 |