November 17, 2021
Date: #2021-11-17
https://www.framer.com/docs/motionvalue/
By manually creating MotionValues you can
useMotion hook을 이용하면 motion에 따라 바뀌는 값들을 알 수 있음. set, get method를 통하여 읽기 쓰기를 할 수 있다. 중요한 것은 react state가 아니라 리렌더링을 하지 않는다. console.log를 통해서 로그를 찍어봐도 값이 변하지 않는다.
const x = useMotionValue(0);
...
<Box
drag={'x'}
style={{ x }}
/>
<Box style={{ x }} />
이렇게 해두면 위의 박스를 드래그할 때 밑의 박스가 따라 움직이게 된다. x 값이 변하는 것을 굳이 봐야겠다하면, useEffect를 통해 이벤트 리스너를 통해 찍어 볼 수 있다.
useEffect(() => {
x.onChange(() => console.log(x.get()));
}, [])
useTransform hook을 이용하면, React Native의 Animate.interpolate처럼 inputRange를 통해 output을 얻을 수 있다.
const scaleValue = useTransform(x, [-800, 0, 800], [2, 1, 0]);
input range와 output range는 같은 길이를 가져야 한다(다르면 에러 발생) 숫자 뿐만 아니라 문자로 지정된 색도 interpolate 가능.
4가지 정보를 알려줌. scrollX, scrollY, scrollXProgress, scrollYProgress. 각각 horizontal, vertical로 scroll 한 거리를 pixel로 알려주고, 얼마자 progress 되었는지 0~1 사이 소수로 알려준다. 여기 주어진 값들도 MotionValue이기 때문에 useTransform을 이용해서, 다른 interpolated된 값들을 얻을 수 있다.
<svg role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor"
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"
class=""></path>
</svg>
일단 fontawesome에서 airbnb 아이콘 svg를 따온다. path의 속성에는 stroke, strokeWidth가 있어 svg의 겉라인을 꾸며줄 수 있다.
<motion.path
initial={{
pathLength: 0.1,
fill: 'rgba(255, 255, 255, 0)',
}}
animate={{
pathLength: 1,
fill: 'rgba(255, 255, 255, 1)',
transition: {
duration: 2,
},
}}
stroke={'white'}
strokeWidth={3}
fill="currentColor"
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>
path 컴포넌트는 motion.path 컴포넌트로 대체하고, 나머지 애니메이션을 꾸며주면 되는데.. pathLength는 path를 얼마나 그릴지라서.. 위의 값을 주면 애니메이션되는 듯이 멋있게 렌더링이 완성이 된다.
여태까지 배운 내용으로는 애니메이션에 transition을 줘서 모든 속성이 같은 transition을 갖도록 했는데, motion 컴포넌트의 transition 속성을 직접 주면 css 속성마다 따로 transition을 줄 수도 있다.
위의 코드에서..
<motion.path
initial={{
pathLength: 0.1,
fill: 'rgba(255, 255, 255, 0)',
stroke: 'rgb(40, 100, 214)',
}}
animate={{
pathLength: 1,
fill: 'rgba(255, 255, 255, 1)',
stroke: 'rgb(120, 53, 123)',
}}
transition={{
default: {
duration: 5,
},
fill: {
delay: 5,
duration: 2,
},
}} />
애니메이션 속성을 위처럼 바꿔볼 수 있는데..transition 속성에서 볼 수 있듯.. fill 속성만 따로 transition을 주는 것도 가능하다.
https://www.framer.com/docs/animate-presence/
Animate components when they’re removed from the React tree. It’s required to enable exit animations because React lacks a lifecycle method that: Notifies components when they’re going to be unmounted and Allows them to defer that unmounting until after an operation is complete (for instance an animation).
보통의 리액트 상황에서는
{ open ? <ShowSomething /> : null}
이런식으로 작성을 하기 때문에 사라질 때 애니메이션은 없는 편이다. 그치만 framer motion 컴포넌트가 AnimatePresence 컴포넌트로 감싸진다면.. exit 라는 속성이 생기게 된다.
예제코드
<AnimatePresence>
{open && (
<Box
variants={boxVars}
initial={'start'}
animate={'end'}
exit={'exit'}
/>
)}
</AnimatePresence>
<button onClick={toggle}>Toggle</button>
exit 속성으로 사라질 때 애니메이션을 줄 수 있다.
Note: Child motion components must each have a unique key prop so AnimatePresence can track their presence in the tree. 문서에 나와있듯.. key를 꼭 줘야 한다. 그래야 AnimatePresence가 DOM tree에서 제거 되는 것을 추적할 수 있다.
In React, changing a component’s key makes React treat it as an entirely new component. So the old one is unmounted before the new one is mounted. So by changing the key of a single child of AnimatePresence, we can easily make components like slideshows 약간의 트릭인 것 같은데, 키 값이 바뀌면 새로운 컴포넌트로 인식하기 때문에 슬라이드처럼 보일 수 있다고 한다.
https://www.framer.com/docs/animate-presence/#usage 예제를 확인함이 좋을 것 같다.
위의 노트와 예제에 해당하는 부분.. #7.12 #7.13
const boxVars = {
start: {
x: 500,
opacity: 0,
scale: 0,
},
end: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
}
},
exit: {
opacity: 0,
scale: 0,
x: -500,
},
};
const [page, setPage] = useState(0);
const next = () => setPage((prev) => (prev === 9 ? 1 : prev + 1));
<div
style={{
display: 'flex',
overflow: 'hidden',
backgroundColor: 'transparent',
}}
>
<AnimatePresence>
{Array.from(new Array(10)).map((_, index) =>
page === index ? (
<Box
key={index}
variants={boxVars}
initial={'start'}
animate={'end'}
exit={'exit'}
>
{index + 1}
</Box>
) : null,
)}
</AnimatePresence>
</div>
<button onClick={next}>Next</button>
대략 위와 같은 코드로 만들어졌는데, AnimatePresence를 이용하여 슬라이더를 쉽게 구현할 수 있었다.
보통 강의 듣기 강의 시작 전에 이번엔 ~~을 할 것이다… 하면 강의를 듣지 않고, 먼저 구현해 보는데, 이번에는 구현할 때 문제가 있었다.
그… position: aboluste
관련된 것이었는데.. 애니메이션이 사라지기는 동안 + 다음 박스가 오는 애니메이션이 같은 div 안에 존재 해버리니까..
이상한 형태로 애니메이션이 진행 되었는데, 강의를 듣고 position: aboluste
를 추가했었어야 되는구나..알게 되었음.
위의 코드는 Array를 이용하여 여러개의 박스를 렌더링 하였지만, 사실상 위에 Note에 해당하는 내용에서 알 수 있듯.. key가 바뀌면 새로운 컴포넌트로 인식을하고 그 이전의 키값을 가지고 있는 컴포넌트는 사라지는 것으로 리액트는 인식을 하기 때문에 위의 코드는
<AnimatePresence>
<Box
key={page}
variants={boxVars(isNext)}
initial={'start'}
animate={'end'}
exit={'exit'}
>
{page}
</Box>
</AnimatePresence>
이와 같이 바뀔 수 있다. 작동은 똑같이 한다.
https://www.framer.com/docs/component/###custom
variants에 일종의 변수가 들어간 함수처럼 작동할 수 있도록 해준다.
const boxVars: Variants = {
start: (isNext: boolean) => ({
x: isNext ? 500 : -500,
opacity: 0,
scale: 0,
}),
end: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
},
},
exit: (isNext: boolean) => ({
opacity: 0,
scale: 0,
x: isNext ? -500 : 500,
transition: {
duration: 1,
},
}),
};
// ...(중략)...
<AnimatePresence exitBeforeEnter custom={isNext}>
<Box
key={page}
custom={isNext}
variants={boxVars}
initial={'start'}
animate={'end'}
exit={'exit'}
>
{page}
</Box>
</AnimatePresence>
이상하게 AnimatePresence에도 custom 값을 줬어야 했다.
이 속성이 없으면. 없어지는 박스와 새로나오는 박스가 겹쳐 벌인다. 이 속성이 있으면, 없어지는 박스가 완전히 없어져야 새로운 박스가 시작된다.
https://www.framer.com/docs/component/##layout-animation
css에 따라 component의 위치가 바뀔 수 있는데.. 위치가 바뀔 때 애니메이션을 하도록.. 해주는 역할을 한다. 공식홈페이지에는 스위치 버튼이 예시로 되어 있는데, 그냥 단순하게 css는 position 바뀌도록만 하고.. layout 속성만 주면 알아서 애니메이션이 된다는..것..
<Box
style={{
justifyContent: clicked ? 'center' : 'flex-start',
alignItems: clicked ? 'center' : 'flex-start',
}}
onClick={() => setClicked((prev) => !prev)}
>
<Circle layout />
</Box>
위의 예시에서 보면 Box가 클릭되면 단순히 위치가 center냐 start냐가 갈리지만, Circle에 layout 속성 하나만 줬을 뿐인데, 애니메이션이 되어버린다..
https://www.framer.com/docs/component/###layoutid
layoutId 있는 것이 react tree에서 사라졌다가 다른 곳에서 나타나면, 이전 위치에서부터 애니메이션을 시켜준다. 이전 컴포넌트가 남아있으면, 새로운 컴포넌트와 동시에 fade된다고(crossfade)..한다..
<Box>{!clicked && <Circle layoutId={'circle'} />}</Box>
<Box>{clicked && <Circle layoutId={'circle'} />}</Box>
클릭하면 박스에 있는 Circle이 왔다갔가 나타나게 하는 코드이지만, layoutId 하나를 추가함으로 엄청난 애니메이션이 만들어진다. 한쪽 박스에 있는 것이 다른 쪽 박스로 이동하는 듯한.. 마술같은 애니메이션이 단 layoutId라는 한 단어*2로 나타난다. 후덜덜..