React JS 마스터클래스(ANIMATIONS ) - 1

짜스의 하루 ·2024년 6월 21일

Framer Motion

Framer Motion은 모션 그래픽과 애니메이션을 구현하기 위한 라이브러리이다.
주로 React 애플리케이션에서 사용되며, 다양한 애니메이션 효과를 쉽게 추가할 수 있도록 도와준다.

npm install framer-motion 으로 설치를 진행하면 된다.

위의 코드처럼 import를 한 다음,
motion을 사용할 때에는 <motion.div>와 같이 태그를 작성해야 사용이 가능하다는 점을 꼭 알고 있자!!


animation Box 만들어보기

여기서 잠깐! 우리는 CSS 속성을 정의할 때, styled-component를 사용했다.
그럼 어떻게 styled-component 랑 motion을 사용할 수 있을 까?


이와 같이 const Box = styled(motion.div) 로 사용하면, motion의 div라는 의미로 styled-component를 적용할 수 있으니 꼭 기억하자!


이렇게 움직이는 박스를 만들어보자

먼저, 처음에 박스가 사라졌다가 회전을 하면서 박스가 생겨나고, 몇초 후에 멈춘다! 라는 것을 알 수 있다.

<Box
  initial={{ scale: 0 }}       // 초기 상태에서의 스타일과 애니메이션 설정
  animate={{ scale: 1, rotateZ: 360 }}   // 애니메이션 동작 중의 스타일과 애니메이션 설정
  transition={{ type: 'spring', delay: 0.5 }}  // 애니메이션 전환 설정
/>
  • initial: 요소의 초기 상태를 설정
    여기서 initial={{ scale: 0 }}는 요소의 초기 스케일(scale)을 0으로 설정한다.
    이는 요소가 처음에는 화면에서 보이지 않고 축소된 상태로 시작한다는 것을 의미한다.

  • animate : 요소가 애니메이션을 통해 어떻게 변화할지 설정한다.
    animate={{ scale: 1, rotateZ: 360 }} 는 요소가 애니메이션 동작 중에 확대(scale: 1)되고, 동시에 Z축 기준으로 360도 회전(rotateZ: 360)하는 것을 의미한다.

  • transition : 애니메이션 전환(transition)을 설정한다.
    transition={{ type: 'spring', delay: 0.5 }}는 이 애니메이션이 스프링(Spring) 타입의 애니메이션이며, 0.5초의 지연(delay: 0.5) 후에 시작된다는 것을 나타낸다.
    스프링 타입의 애니메이션은 자연스럽고 탄성 있는 움직임을 제공하며, 지연 설정은 애니메이션 시작 전에 대기 시간을 설정하는 것이다.

만약, 애니메이션이 많아지면 컴포넌트 안에 적어야 할 것들이 엄청 많아지면 코드가 복잡해질 수 있다
--> 깔끔하게 코드를 작성하는 방ㅂ덥은 뭐가 있을까?

Framer Motion의 variants 기능을 사용하면 된다
variants여러 가지 상태(variants) 를 정의하고, 각 상태에서의 스타일과 애니메이션을 설정할 수 있다.

먼저 상수 myVars정의를 해보자

  • myVars 상수는 start와 end라는 두 개의 상태를 정의한다.
  • start 상태는 { scale: 0 }으로 설정되어 있어, 요소의 초기 스케일(scale)을 0으로 지정한다.
  • end 상태는 { scale: 1, rotateZ: 360, transition: { type: 'spring', delay: 0.5 } }으로 설정되어 있어, 요소가 애니메이션 동작 중에는 확대(scale: 1)되고 Z축을 중심으로 360도 회전(rotateZ: 360)한다.
    또한, 스프링(Spring) 타입의 애니메이션을 적용하며, 애니메이션 시작 전에 0.5초의 지연(delay: 0.5)이 있다.

Box 컴포넌트에 variants={myVars}를 설정하여 애니메이션의 상태를 myVars에서 정의한 상태들로 관리한다.

  • initial="start" prop을 통해 초기 상태(initial state)를 start로 설정한다. 따라서 요소가 렌더링될 때 초기 스케일(scale: 0)로 시작한다.

  • animate="end" prop을 통해 애니메이션을 적용할 상태를 end로 설정한다. 이 prop은 요소가 애니메이션 동작 중에는 확대(scale: 1) 및 회전(rotateZ: 360)되도록 지정한다. 또한, transition 설정에 따라 스프링(Spring) 타입의 애니메이션과 0.5초의 지연이 적용된다.

이렇게 하나의 상수에 필요한 animation에 대해서 설정해두고, variant 속성을 이용해 가간편하게 다양한 애니메이션을 구현할 수 있다.


이제 이렇게 움직이는 박스를 만들어 볼 것이다!

간단하게 먼저 생각해보자면
<Box> 컨테이너 안에 <Circle> 컨테이너 4개가 있을 것이다
그리고 animation은 먼저 Box가 등장한 뒤, circle이 한개씩 등장하는 모습이다.


그럼 우선 <Box/>, <Circle/> 컴포넌트에서 사용할 Variant들을 정의해 보자!

1. boxVariants: Box 컴포넌트의 애니메이션 설정

  • start 상태:
    opacity: 0 : 투명도를 0으로 설정하여 요소가 보이지 않게 만든다.
    scale: 0.5 : 요소의 크기50%로 축소

  • end 상태:
    opacity: 1 : 투명도를 1로 설정하여 요소가 완전히 보이도록 한다.
    scale: 1 : 요소의 크기를 원래 크기로 설정한다.

  • transition (애니메이션 전환 설정):
    type: 'spring' : 스프링 기반 애니메이션으로 자연스럽고 탄력적인 효과를 제공한다.
    bounce: 0.5 : 스프링 애니메이션의 탄력성을 조절한다. 값이 클수록 더 많이 튀귄다.
    duration: 1 : 애니메이션이 1초 동안 진행되도록 설정
    delayChildren: 1: 자식 요소의 애니메이션이 시작되기 전에 1초 지연을 적용한다.
    staggerChildren: 0.2: 각 자식 요소의 애니메이션이 0.2초 간격으로 시작되도록 설정한다.

여기서 가장 중요하게 살펴봐야 할 것은 delayChildren, staggerChildren 이다!

delayChildren
우리는 현재 Box가 1초 동안 진행되는 것을 알고 있다.
---> 즉, circle은 Box의 animation이 끝나고 animation이 실행되야 한다.
--> Box의 animation (1초)가 끝난 뒤, 자식 요소의 애니메이션을 진행한다! 라는 말을 의미한다고 생각하면 된다.

staggerChildren
circle은 왼쪽 위에 원부터 하나씩 등장한다.
만약 원래라면, 원에다 5초, 1초 1.5초, 2초 씩 delay를 주거나 해야하지만, staggerChildren을 사용하게 되면, 0.2 간격으로 원이 등장하게 된다!

2. circleVariants: Circle 컴포넌트의 애니메이션 설정

  • start 상태:
    opacity: 0 : 투명도를 0으로 설정하여 요소가 보이지 않게 만든다.
    y: 10 : 요소를 y축 방향으로 10px 아래로 이동시킨다.

  • end 상태:
    opacity: 1 : 투명도를 1로 설정하여 요소가 완전히 보이도록 한다.
    y: 0: 요소를 원래 위치로 되돌아 오게 한다.


어? 근데 왜 <Circle/> 컴포넌트는 initial , animate를 적용하지 않았는데 왜 적용이 되는거지?

부모 컴포넌트인 Box에서 initial="start"와 animate="end"를 지정했다.
Box는 자식 컴포넌트인 Circle에게도 이 설정을 전달하게 된다.
Box는 variants 객체의 상태 전환(start에서 end로 전환)을 수행하며, 이 전환이 Box 내의 모든 자식 컴포넌트(Circle)에도 적용된다.

중요한 내용을 다시 한번 정리해보자
애니메이션 상속의 주요

  • 부모-자식 관계: 부모 컴포넌트인 Box는 initial 및 animate 속성을 통해 자식 컴포넌트인 Circle에 애니메이션 상태를 상속한다. 부모 컴포넌트의 상태 전환에 따라 자식 컴포넌트도 동일한 애니메이션 상태 전환을 따르게 된다.
    (그래서 circleVariants의 상태를 지정할 때 start, end의 이름으로 지정해 둔것이다)

delayChildren과 staggerChildren :

  • delayChildren : 부모 컴포넌트(Box)의 애니메이션이 완료된 후 자식 컴포넌트(Circle)의 애니메이션이 시작되는 시간을 지연시킨다.
  • staggerChildren : 자식 컴포넌트들이 순차적으로 애니메이션을 시작하도록 간격을 조절한다. 이를 통해 각 Circle이 0.2초 간격으로 애니메이션을 시작하게 된다.

자식 컴포넌트에서의 애니메이션 설정:

  • 자식 컴포넌트 (Circle): initial과 animate 속성을 직접 설정하지 않았지만, 부모 컴포넌트의 initial과 animate 상태 이름(start, end) 을 상속받아 동일한 상태 전환을 따른다.
    Circle은 circleVariants의 start에서 end로 전환된다.

whileHover, whileTap

이번에는 마우스를 올렸을 때, 90도로 회전하고,
클릭했을 때 크기가 줄어들면서~ 원 모양이 되는 형태를 한번 만들어 보겠다

이떄는 이벤트를 적용해야 하는데 (이벤트라고 하면 onClick, hover...이런것들이 생각이 나는데) , motion에서는 whileHover, whileTap으로 사용할 수 있다.

whileHover = {{ scale : 1.5, rotateZ : 90}} 
whileTap = {{ scale : 1, borderRadius : '100px' }}

로 정의할 수 있지만, variants의 기능을 사용해서 boxVariants에 hover, tap으로 각각의 animation을 지정해두고, whileHover ="hover", whileTap="tap" 으로 지정해 두었다!

간단하게 설명하자면, hover을 했을 때, 크기는 1.5배에 90도 회전을 하고,
거기서 tap을 했을 떄, 크기는 다시 원래대로 돌아오고, borderRadius를 '100px'를 준다. 라고 생각하면 될 것 같다!

Drag

어제까지 열심히 만들었던 toDoBoard에 기능으로 있었던 Drag를 motion에서는 어떻게 적용할 까?

정말 간단하게
<Box drag .../> 즉 drag하고 싶은 컴포넌트에 drag만 적어두면 끝..!
정말 간단하다!

그럼 드래그 했을 때, 배경색상이 변경되도록 한번 코드를 짜보자!

  • backgroundColor: 'rgb(46,204,113)' : 요소가 드래그되는 동안 backgroundColor가 'rgb(46,204,113)'으로 변경된다.
  • { duration: 10 }: 애니메이션이 10초 동안 서서히 진행됨을 의미

--> 드래그가 시작되면 요소의 배경색이 바로 rgb(46,204,113)으로 변경되기 시작하고, 이 변화가 완전히 완료되기까지 10초가 걸리는 것을 의미한다.


10초 동안 서서히 색깔이 rgb(46,204,113) 색으로 변경되는 것을 확인할 수 있다!

근데, 지금은 이곳 저곳 다 드래그가 되서 쭉 드래그가 되서 화면 밖으로 빠지기도 한다. 이를 제한을 줘보도록 하겠다!

  1. 저 투명한 박스 안에서만 이동이 가능하도록 , 2. 드래그 후 마우스를 놓으면 원래 위치 (가운데)로 이동하도록

이 두가지 기능을 넣어보겠다


여기서 뜬금없이 useRef를 사용한다고 생각할 수도 있다 (나도 그랬으니까!)

useRef가 BiggerBox 컴포넌트의 DOM 요소를 참조하여 Box의 드래그 제한을 설정하는 데 사용된다.
구체적으로, dragConstraints 속성에 useRef로 참조한 DOM 요소를 전달함으로써 드래그 가능한 범위를 설정한것이다.

useRef 훅 사용 방식

  • useRef로 참조 객체 생성:
    const biggerBoxRef = useRef<HTMLDivElement>(null);
    useRef는 초기 값이 null인 참조 객체를 생성한다.
    이 객체는 current 속성을 가지며, 이후 DOM 요소를 참조하게 된다.
  • ref 속성으로 DOM 요소에 참조 전달:
    <BiggerBox ref={biggerBoxRef}>
    ref 속성을 통해 BiggerBox 요소에 biggerBoxRef 참조를 전달한다. 이렇게 하면 BiggerBox 요소가 DOM에 마운트될 때 biggerBoxRef.current가 해당 DOM 요소를 참조하게 된다.
  • dragConstraints에 참조 전달:
    <Box drag dragConstraints={biggerBoxRef} />
    dragConstraints 속성으로 biggerBoxRef를 전달하여, Box가 드래그 가능한 범위를 BiggerBox의 경계로 제한한다.
    dragConstraints는 Box의 드래그 범위를 biggerBoxRef.current로 지정된 요소의 경계 내로 제한한다.

useRef를 사용하여 BiggerBox의 DOM 요소를 참조하고, 이 참조를 dragConstraints에 전달하여 드래그 가능한 범위를 BiggerBox로 제한한 것

dragConstraints={biggerBoxRef}: drag 상태의 요소가 특정 영역 내에서만 드래그되도록 제한
dragSnapToOrigin: 드래그가 끝난 후 요소를 원래 위치로 되돌린다.


MotionValue

MotionValues는 애니메이션 값의 상태(state)와 속도(velocity)를 추적한다.
모든 모션 컴포넌트는 내부적으로 MotionValues를 사용하여 애니메이션 값의 상태와 속도를 추적하게 되는데, 일반적으로 이들은 자동으로 생성된다.
(MotionValue는 React State가 아니기 때문에 Motion Value값이 바뀌어도 리랜더링이 일어나지 않는다.)

<Wrapper>
    <Box drag="x" dragSnapToOrigin />
</Wrapper>

이렇게 속성을 제공해주어 <Box/> 컴포넌트가 x축으로만 이동이 가능하고, 드래그를 넣으면 원래상태로 돌아올 수 있도록 속성을 제공해주었다.

그럼 이제 x의 값을 한번 가져와볼까 ?

useMotionValue 훅을 사용하여 x라는 MotionValue를 생성하고 있다.
useMotionValue(0) 은 초기값으로 0을 가지는 MotionValue 객체를 생성한 것이다.

이를 사용하기 위해서는
<Box> 컴포넌트의 style 속성을 설정할 때, style={{ x }}와 같이 객체 형태로 MotionValue를 직접 참조할 수 있다.

MotionValue를 사용할 때 style={{ x }}와 같이 직접적으로 사용하는 것은 주로 Framer Motion에서 애니메이션을 구현할 때의 방식이다.

이렇게 콘솔을 한번 찍어볼까 ?
하면 콘솔에 아무것도 찍히지 않는 것을 확인할 수 있다.

Framer Motion의 useMotionValue 훅을 사용하여 생성된 x는 일반적인 JavaScript 변수나 객체가 아닌, Framer Motion이 제공하는 MotionValue 객체이다.
이 객체는 내부적으로 애니메이션 값을 관리하고, 변경할 때마다 UI를 업데이트하게 된다. 따라서 console.log(x)로는 MotionValue 객체의 상태를 직접 확인할 수 없다.

그럼 어떻게 x의 값을 얻어올 수 있을 까?

useEffect(() => {
    x.onChange(() => console.log(x.get()));
  });

x.onChange : x 객체의 onChange 메서드를 호출하여, MotionValue의 값이 변경될 때마다 호출되는 콜백 함수를 등록한다.
즉, x의 값이 변경 시에 console.log(x.get())를 호출하여 MotionValue의 현재 값을 콘솔에 출력할 수 있게 되었다!


엄청 잘~~~ 찍히는 것을 확인할 수 있다.

그럼, 버튼을 누르면 내가 설정한 x값으로 이동시킬 수 있지 않을까?

<button onClick={() => x.set(500)}>나를 눌러봐!</button>

버튼에 onClick 이벤트를 주어서, 클릭했을 때, x의 값을 set-> 500으로 설정한다!

y도 한번 적용시켜볼까 ?
이렇게 하면 이제 버튼을 누를 때, x -> 300, y -> 300 좌표로 이동하는 것을 볼 수 있다!


useTransform

useTransform은 Framer Motion 에서 제공하는 훅(Hook) 중 하나로, MotionValue의 값을 변환하는 데 사용된다.

useTransform의 구조
const scale = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);

  • 입력 값: x는 useMotionValue(0)을 통해 생성된 MotionValue이다.
  • 출력 값 계산: scale은 x의 값에 따라 변환된 값이다.

useTransform(x, [-800, 0, 800], [2, 1, 0.1])에서:

  • 입력 범위: x의 값이 -800에서 0 사이일 때, 출력 값 scale은 2에서 1로 선형적으로 변환된다.
  • 출력 범위: x의 값이 0에서 800 사이일 때, 출력 값 scale은 1에서 0.1로 선형적으로 변환된다.
  • x의 값이 -800 미만이거나 800을 초과하면 범위 외의 값이므로, -800 이하일 때는 2를, 800 이상일 때는 0.1을 유지한다.

(여기서 scale이라는 이름은 아무렇게나 지어도 된다.)

<Box style={{ x, scale: scale }} drag="x" dragSnapToOrigin />

  • style 속성: JSX에서 요소에 스타일을 적용할 때 사용하는 속성이다. 객체 형태로 지정하며, CSS 속성명과 그 값을 포함할 수 있.
  • x: x는 MotionValue 객체이다. useMotionValue(0)을 통해 생성되었으며, Box 요소의 가로 위치를 제어하는 데 사용된다. x의 값이 변경될 때마다 Box 요소의 가로 위치도 동적으로 변경된다.
  • scale: scale 역시 MotionValue 객체이다.
    useTransform을 통해 x의 값에 따라 변환된 크기 값을 가진다.
    예를 들어, x 값이 -800에서 0 사이에 있으면 scale 값은 2에서 1 사이로 변환되고, 이렇게 변환된 scale 값은 Box 요소의 크기를 동적으로 조절하는 데 사용된다.
profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글