useTransform를 이용해서 다양한 motion을 적용해보자!

1 . x 축을 중심으로 360도를 회전하는 motion을 만들어보자.

const rotateZ = useTransform(x, [-800, 800], [-360, 360]) 은 x 값이 -800에서 800으로 변할 때, rotateZ 값이 -360도에서 360도로 변하게 한다.
--> 즉, x 값이 -800일 때 rotateZ는 -360도, x 값이 800일 때 rotateZ는 360도가 된다고 이해하면 된다.
2 . 상자가 이동할 때마다 배경화면도 변경해보자!
x 값이 -800에서 800으로 변화할 때, gradient의 값이 처음에서부터 두번째 값 사이에서 전환된다.
여기서도 마찬가지로 상수값이 background이기 때문에
styled = {{background : background}}로 적지 않고 styled = {{background }} 로 적어도 적용이 된다!

이번에는 스크롤을 했을 때, 상자의 크기가 커지는 것을 만들어보려고 한다!
이때는 useViewportScroll 을 사용해야 한다!
사용자가 브라우저 창(뷰포트)을 스크롤할 때 발생하는 스크롤 위치를 추적하는 데 사용된다.
이를 통해 스크롤 위치에 따라 애니메이션을 조정하거나 다양한 스크롤 기반 인터랙션을 구현할 수 있다.
useViewportScroll은 두 개의 객체를 반환한다:
그리고 이를
const { scrollX, scrollY, scrollXProgress, scrollYProgress } = useViewportScroll(); 로 사용한다!

useViewportScroll
useTransform
보너스 스크롤바 만들어보기!

이 속성을 style ={{ width }} 에 주면서, width 값은 스크롤 위치에 따라 자동으로 '0%'에서 '100%' 사이로 변화하게 된다. 
위에 잘 보면 스크롤 내리자 빨간색 선이 꽉 찼다가 스크롤을 올리자 스크롤이 다시 줄어드는 것을 확인할 수 있다!

에어비엔비 로고를 animation을 줘보려고 한다!
웹사이트에 들어가다보면 이렇게 좀 멋스럽게 (?) 로고가 생겨난다거나, 움직인다거나 하는 것을 볼 수 있었는데, 에어비엔비 로고로 만들어보러고 한다
<Svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path d="M224 373.1c-25.2-31.7-40.1-59.4-45-83.2-22.6-88 112.6-88 90.1 0-5.5 24.3-20.3 52-45 83.2zm138.2 73.2c-42.1 18.3-83.7-10.9-119.3-50.5 103.9-130.1 46.1-200-18.9-200-54.9 0-85.2 46.5-73.3 100.5 6.9 29.2 25.2 62.4 54.4 99.5-32.5 36.1-60.6 52.7-85.2 54.9-50 7.4-89.1-41.1-71.3-91.1 15.1-39.2 111.7-231.2 115.9-241.6 15.8-30.1 25.6-57.4 59.4-57.4 32.3 0 43.4 25.9 60.4 59.9 36 70.6 89.4 177.5 114.8 239.1 13.2 33.1-1.4 71.3-37 86.6zm47-136.1C280.3 35.9 273.1 32 224 32c-45.5 0-64.9 31.7-84.7 72.8C33.2 317.1 22.9 347.2 22 349.8-3.2 419.1 48.7 480 111.6 480c21.7 0 60.6-6.1 112.4-62.4 58.7 63.8 101.3 62.4 112.4 62.4 62.9 .1 114.9-60.9 89.6-130.2 0-3.9-16.8-38.9-16.8-39.6z" />
</Svg>
이 상태로 불러오게 되면,

정말 이상한(?) 로고가 나타나게 된다.
<motion.path
initial={{ pathLength: 0, fill: 'rgba(255,255,255,0' }}
animate={{
pathLength: 1,
fill: 'rgba(255,255,255,1',
transition: { duration: 2 },
}}
d="M224 373.1c-25.2-31.7-40.1-59.4-45-83.2-22.6-88 112.6-88 90.1 0-5.5 24.3-20.3 52-45 83.2zm138.2 73.2c-42.1 18.3-83.7-10.9-119.3-50.5 103.9-130.1 46.1-200-18.9-200-54.9 0-85.2 46.5-73.3 100.5 6.9 29.2 25.2 62.4 54.4 99.5-32.5 36.1-60.6 52.7-85.2 54.9-50 7.4-89.1-41.1-71.3-91.1 15.1-39.2 111.7-231.2 115.9-241.6 15.8-30.1 25.6-57.4 59.4-57.4 32.3 0 43.4 25.9 60.4 59.9 36 70.6 89.4 177.5 114.8 239.1 13.2 33.1-1.4 71.3-37 86.6zm47-136.1C280.3 35.9 273.1 32 224 32c-45.5 0-64.9 31.7-84.7 72.8C33.2 317.1 22.9 347.2 22 349.8-3.2 419.1 48.7 480 111.6 480c21.7 0 60.6-6.1 112.4-62.4 58.7 63.8 101.3 62.4 112.4 62.4 62.9 .1 114.9-60.9 89.6-130.2 0-3.9-16.8-38.9-16.8-39.6z"
/>
이렇게 motion.path 로 Framer Motion을 사용가능하게 제공해 준 다음,
initial: 초기 상태를 설정
animate: 애니메이션 효과를 설정
이렇게 설정하게 되면, transition: { duration: 2 } 는 모든 애니메이션이 지속 되는 시간이 2초로 설정되기 때문에, pathLength 따로, fill 따로 진행되지 않는다.
그럼 어떻게 해야할까?
<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: 5 },
}}
d="M224 373.1c-25.2-31.7-40.1-59.4-45-83.2-22.6-88 112.6-88 90.1 0-5.5 24.3-20.3 52-45 83.2zm138.2 73.2c-42.1 18.3-83.7-10.9-119.3-50.5 103.9-130.1 46.1-200-18.9-200-54.9 0-85.2 46.5-73.3 100.5 6.9 29.2 25.2 62.4 54.4 99.5-32.5 36.1-60.6 52.7-85.2 54.9-50 7.4-89.1-41.1-71.3-91.1 15.1-39.2 111.7-231.2 115.9-241.6 15.8-30.1 25.6-57.4 59.4-57.4 32.3 0 43.4 25.9 60.4 59.9 36 70.6 89.4 177.5 114.8 239.1 13.2 33.1-1.4 71.3-37 86.6zm47-136.1C280.3 35.9 273.1 32 224 32c-45.5 0-64.9 31.7-84.7 72.8C33.2 317.1 22.9 347.2 22 349.8-3.2 419.1 48.7 480 111.6 480c21.7 0 60.6-6.1 112.4-62.4 58.7 63.8 101.3 62.4 112.4 62.4 62.9 .1 114.9-60.9 89.6-130.2 0-3.9-16.8-38.9-16.8-39.6z"
/>
transition 속성 : 애니메이션의 지속 시간과 지연 시간을 설정한다.
default: { duration: 5 } : 기본적인 애니메이션의 지속 시간을 5초로 설정fill: { duration: 2, delay: 5 } : fill 속성에 대한 애니메이션의 지속 시간을 2초로 설정하고, 5초 후에 시작되도록 지연 시간(delay)을 5초로 설정한다.즉, 기본적인 애니메이션의 지속 시간은 5초로 설정하지만, fill은 제외이다.
fill 속성은 fill: { duration: 2, delay: 5 } 로 따로 지정을 해두었기 때문이다!
--> fill을 제외한 난머지 애니메이션은 지속시간이 5초로 설정되고, fill은 5초 이후에, 즉 나머지 애니메이션이 진행된 후, 실행이 된다!

요렇게 variants 속성을 이용해 코드를 깔끔하게 정리할 수도 있다!
이번엔 구글 로고도 한번 animation을 줘보도록 하겠다!

다른 속성들은 이전에 적용했던 것과 똑같지만.
rotateZ : 360 속성을 주어서 회전을 할 수 있도록 해보았다

생각보다 더 이쁜 느낌이다 호호
framer-motion 라이브러리에서 제공하는 컴포넌트로, React의 컴포넌트가 DOM에서 추가되거나 제거될 때 애니메이션 효과를 쉽게 구현할 수 있도록 도와준다. 보통 요소가 마운트(추가)되거나 언마운트(제거)될 때 애니메이션을 추가하는 데 사용한다.

우선 간단하게 버튼을 눌렀을 때, <Box/> 컴포넌트가 열릴 수 있도록 코드를 작성해 두었다.
지금은 어떠한 애니메이션을 적용하지 않아서 정말 딱딱하게 열렸다가 닫혔다가 한다.
그럼 여기서 AnimatePresence를 적용하려면 어떻게 해야할까?
<AnimatePresence> {showing && <Box />}</AnimatePresence> 이렇게 animation을 적용할 부분에 <AnimatePresence>로 감싸주어야 한다.
<AnimatePresence> 로 감싸면: showing 상태에 따라 <Box /> 컴포넌트가 추가되거나 제거될 때, 해당 영역에서 애니메이션을 가능하게 해준다.

variants의 속성을 사용하여 initial, animate, exit의 속성들을 모두 정의해두고, 적용시켰다.
여기서 exit 은 사라질 때 적용할 속성인 것이다.
즉, 등장할 때, initial -> animate , 사라질 때, animate -> leaving의 효과로 변경된다고 생각하면 된다. 
기존에 만들었던 창들보다 확실히 역동적이고 animation이 잘 보이는 것을 알 수 있다 !

이번에는 버튼을 눌렀을 때 슬라이더 형태로 box가 생겨나고 사라지는 것을 만들어보자!

먼저 코드를 간단하게 살펴보자면.
map 함수는 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 배열의 각 요소 i에 대해:
visiable === i일 때, 즉 현재 상태와 상자의 번호가 일치할 때만 <Box> 컴포넌트를 렌더링한다.
<Box> 컴포넌트는 variants, initial, animate, exit props로 애니메이션 설정을 받아 애니메이션 효과를 적용한다.
이후 , onClick 이벤트에 nextPlease함수를 지정해주었으며. nextPlease 함수는 visiable 값을 1씩 증가시키게 된다.
만약 visiable이 10이면, 더 이상 증가하지 않고 10으로 유지된다. 이 함수는 버튼 클릭 시 호출된다.
variants로 설정한 애니메이션을 살펴보자
invisiable
visiavle
exit
varients로 설정을 주어서
<Box variants={box} initial="invisiable" animate="visiavle" exit="exit"/> 로 지정해 두었다!
이번에는 반대로 돌아가는 버튼을 생성해볼 예정이다.
여기서 생각해볼 수 있는 점이 ( 그럼 invisiable의 x의 값과, exit의 x의 값만 변경하면 되는데!)
맞다! 이떄 사용할 수 있는게 custom 이라는 속성이다!
framer-motion에서 custom 속성은 사용자 정의 데이터를 애니메이션에 전달하여 각 애니메이션 인스턴스마다 서로 다른 애니메이션 동작을 적용할 수 있게 하는 기능을 의믜한다.

새로 생성된 상태관리는 const [back, setBack] = useState(false)
---> back은 상자가 이전으로 이동(true)하거나 다음으로 이동(false)하는 방향을 관리.
애니메이션 방향 결정: custom을 사용하여 back 상태에 따라 상자의 애니메이션 방향을 다르게 설정한다. back이 true이면 상자가 왼쪽에서 등장하여 오른쪽으로 사라지며, false이면 오른쪽에서 등장하여 왼쪽으로 사라지게 된다.
AnimatePresence와 motion: AnimatePresence의 custom 속성을 통해 전달된 back 값을 사용하여 motion.div의 애니메이션 방향을 동적으로 변경한다.
여기서 back이란 ?

여기서 정의한 back과 동일하다!
useState로 상태 관리:
App 컴포넌트에서 const [back, setBack] = useState(false);를 통해 back이라는 상태를 정의하고, 기본값을 false로 설정한다.
custom 속성에 상태 전달:
AnimatePresence의 custom={back}으로 back 상태를 전달한다.
이 custom 값은 AnimatePresence의 자식 컴포넌트로 전달된다.
Box 컴포넌트에서 custom 사용:
Box 컴포넌트(motion.div 확장)에 custom={back}으로 back 값을 전달한다.
이 custom 값은 variants의 콜백 함수에서 접근할 수 있다.
variants의 함수에서 custom 접근:
variants 객체의 entry와 exit 함수는 첫 번째 인수로 custom 속성을 통해 전달된 back 값을 받는다.
이런식으로 이어진다고 생각하면 된다.
entry와 exit 함수의 back 매개변수는 custom 속성을 통해 전달된 back 상태이다.
entry: (back) => ({ x: back ? -800 : 800, ... }) : back이 true일 때 -800으로 이동하여 상자가 왼쪽에서 등장하고, false일 때 800으로 이동하여 오른쪽에서 등장한다.
exit: (back) => ({ x: back ? 800 : -800, ... }) : back이 true일 때 800으로 이동하여 상자가 오른쪽으로 사라지고, false일 때 -800으로 이동하여 왼쪽으로 사라진다.
즉, 오른쪽에서 등장하면 왼쪽으로 사라지고 왼쪽에서 등장하면 오른쪽으로 사라지는 애니메이션을 생성한 것과 같다.
<Box/> 컴포넌트에 <Circle/> 컴포넌트가 있다고 가정해보자
이제, 화면을 클릭했을 때, 번갈아가면서 원이 생기도록 해볼 것이다
(생기고-사라지고, 사라지고-생기고)이런 느낌!
정말 ~~~ 간단하게
<Box/> 컴포넌트를 감싸고 있는 wrapper에 onClick 이벤트를 주었으며 이는 현재 clicked 상태를 반전시키는 함수 (true면 false로, false면 true로).
Wrapper 내부에 두 개의 Box 컴포넌트가 있으며, 각각의 Box 컴포넌트는 Circle을 조건부로 렌더링하게 되는데,
요렇게 이루어지고 있다.
여기서 중요하게 생각해야 할 점은 --> 두개의 <Circle/>컴포넌트는 각각 다른 컴포넌트이다.
<Circle/> 컴포넌트에 layoutId 속성 추가

Circle 컴포넌트는 layoutId를 공유하게 되는데, Framer Motion의 AnimateSharedLayout 기능을 활용하여 애니메이션이 매끄럽게 동작하도록 한다.
layoutId 속성 덕분에 Framer Motion은 두 Circle이 동일한 요소임을 인식하고 자연스러운 애니메이션을 적용하게 된다.
이전에는 각각 다른 컴포넌트로 인식해 사라졌다 생겼다만 반복했다면, layoutId를 적용해 Circle 컴포넌트가 동일한 컴포넌트임을 인식하고, 두 원이 같은 원임을 인식하고 있다
layoutId 없이 Circle 컴포넌트를 전환하는 경우, React는 Circle을 각각의 상태 (clicked의 true/false)에 따라 렌더링하거나 제거하게 된다.
이 경우, React는 첫 번째 Circle과 두 번째 Circle을 완전히 다른 컴포넌트로 인식하게 되면서, 이로 인해 상태 전환 시 Circle이 사라지고, 다시 생겨나는 것처럼 보이게 된다.
layoutId 적용 후 --> layoutId를 적용하면, Circle 컴포넌트가 동일한 컴포넌트임을 인식하게 된다. 두 Box에서 나타나는 Circle은 서로 다른 인스턴스가 아닌, 동일한 layoutId를 가진 하나의 컴포넌트로 간주된다. 이로 인해 애니메이션 효과가 두 컴포넌트 사이를 부드럽게 전환하는 것처럼 보이게 된다.

layout 속성을 이용해서 요런 움직이는 layout 을 만들어보자!
먼저, <Box/> 컴포넌트에 Grid 속성을 적용해주었다.
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
width: 50vw;
gap: 10px;
div:first-child,
div:last-child {
grid-column: span 2;
}
`;
grid-template-columns: repeat(3, 1fr);:
그리드 컨테이너를 3개의 동일한 너비의 열로 설정한다.
1fr은 사용 가능한 공간의 일부를 의미하며, 각 열이 그리드 컨테이너의 너비를 균등하게 차지하게 된다.
grid-column: span 2;:
지정된 요소가 2개의 열을 차지하도록 설정한다.
--> 첫 번째 자식 요소와 마지막 자식 요소는 각각 2개의 열을 차지한다!

AnimatePresence:
조건부 렌더링된 자식 요소(Overlay)의 진입 및 퇴장 애니메이션을 관리한다.
clicked가 true일 때만 Overlay를 렌더링한다.
Overlay:
배경 색상이 애니메이션된다.
--> initial, animate, exit 을 통해 배경 색상이 투명에서 어두운 회색(rgba(0,0,0,0.7))으로 변하게 된다.
이 컴포넌트는 Box를 자식으로 가지며, Box는 layoutId가 "layout"로 설정되어 있어 기존 Box와 애니메이션 트랜지션이 된다.
코드에서 사용된 주요 framer-motion 기능:
1. layoutId:
2 . AnimatePresence:
3 . initial, animate, exit:
애니메이션 흐름:
로 이루어지게 된다!
그럼, 다른 Box들도 layout기능이 가능하도록 기능을 줘보겠다!

const [id, setId] = useState<null | number>(null); :
<Grid>
{[1, 2, 3, 4].map((n) => (
<Box onClick={() => setId(n)} key={n} layoutId={n + ''} />
))}
</Grid>
애니메이션 흐름:
초기 상태:
그리드에 4개의 Box가 배치되어 있으며, id는 null이므로 오버레이는 표시되지 않는다.
Box 클릭 시:
클릭된 Box의 ID가 setId를 통해 설정된다.
AnimatePresence에 의해 Overlay가 표시된다.
배경색 애니메이션: 투명(rgba(0,0,0,0))에서 반투명 검정(rgba(0,0,0,0.7))으로 변경.
클릭된 Box가 오버레이로 이동하고 크기가 조정된다.
Overlay 클릭 시:
setId(null)이 호출되어 id가 null로 설정된다
AnimatePresence에 의해 Overlay가 사라된다
배경색 애니메이션: 반투명 검정(rgba(0,0,0,0.7))에서 투명(rgba(0,0,0,0))으로 변경.
Box는 원래의 그리드 위치로 돌아간다.
결과를 확인해보자!
이렇게 <Box/> 컴포넌트와 overlay에 속한 <Box/> 컴포넌트가 layoutId로 연결되어 애니메이션이 이루어지는 것을 확인할 수 있다!