상세 컨텐츠

본문 제목

[React] Framer Motion

React

by 래모 2022. 7. 20. 23:14

본문

https://www.framer.com/motion/

 

Production-Ready Animation Library for React | Framer Motion

Framer Motion is a production-ready React animation and gesture library.

www.framer.com

예쁜 애니메이션을 만들어보자~~~

 

 


설치방법

npm i framer-motion

 

사용

import {motion} from 'framer-motion';
import styled from 'styled-components';

const Box = styled(motion.div)`
`;

styled-components와 함께 사용하려면 

styeld(motion.태그이름)

이렇게 사용한다.


Animation

const boxVariants = {
  start: {
    scale: 0
  },
  end: {
    scale: 1,
    rotateZ: 360,
    transistion: {
      type: "spring",
      mass: 1,
    }
  }

}

function App() {
  return (
    <Wrapper>
      <Box variants={boxVariants} initial="start" animate="end" />
    </Wrapper>
  );
}

 

transition

 1️⃣ transition={{ ease: "easeOut", duration: 2 }}
 2️⃣ transition={{ type: "spring", stiffness: 100 }}
 3️⃣ transition={{ yoyo: infinity | 10  }}

transition의 type은 tween, spring, inertia 가 있다.

여기서 spring의 경우 stiffness으로 추가 설정 가능하다(스프링의 강도)

 

yoyo속성을 통해 변경되는 사항을 여러번 반복하여 나타낼 수 있다.

infinity입력시 무한 반복


Variants

const boxVariants = {
  start: {
    opacity: 0,
    scale: 0.5,
  },
  end: {
    scale: 1,
    opacity: 1,
    transition: {
      type: "spring",
      duration: 0.5,
      bounce: 0.5,
      delayChildren: 0.5,
      staggerChildren: 0.1
    }
  }
}

const circleVariants = {
  start: {
    opacity: 0,
    y: 10
  }, 
  end : {
    opacity: 1,
    y:0,
  }
}

function App() {
  return (
    <Wrapper>
      <Box variants={boxVariants} initial="start" animate = "end" > 
      // initial과 animate에는 boxVariant에서 설정했던 이름이 똑같이 들어가야함
        <Circle variants={circleVariants}/>
        <Circle variants={circleVariants}/>
        <Circle variants={circleVariants}/>
        <Circle variants={circleVariants}/>
      </Box>
    </Wrapper>
  );
}

부모 컨포넌트가 자식 컴포넌트의 animation을 제어할 수도 있다.

자식 컴포넌트의 variants에 아무것도 적지 않으면 부모 컴포넌트의 variants를 그대로 가져온다

부모로부터 variant 변경 사항을 상속하지 않도록 하려면 

inherit: false

로 설정해야함

 

delayChildren

딜레이 시간 후에 하위 애니메이션이 시작된다

 

staggerChildren

하위 컴포넌트의 애니메이션 지속 시간만큼 시차를 둘 수 있다

 

예를 들어, staggerChildren이 0.01이면

첫 번째 자식은 0초,

두 번째 자식은 0.01초,

세 번째 자식은 0.02초 지연되는 식.

 

더보기

강의에 잠깐 나와서 추가로 적어봄

 

place-items (Container Properties)
justify-items과 align-items를 합친 축약형

place-self (Item Properties)
justify-self와 align-self를 합친 축약형

 

 


Gestures

const boxVariants = {
  hover: {scale: 1.5, rotateZ: 180},
  click: {scale: 1, borderRadius: "100px"},
  drag: {backgroundColor : "rgb(253, 121, 168)", transition: {duration:10 }}
}


function App() {
  const biggerBoxRef = useRef<HTMLDivElement>(null)
  return (
    <Wrapper>
      <BiggerBox ref = {biggerBoxRef}>
        <Box 
          drag 
          dragSnapToOrigin
          dragElastic={0}
          dragConstraints={biggerBoxRef}
          variants = {boxVariants} 
          whileHover="hover" 
          whileDrag = "drag" 
          whileTap="click"/>
      </BiggerBox>
     </Wrapper> 
  );
}

 

while 속성

  • whileHover : 커서가 컴포넌트 위로 이동하거나 떠날 때 동안의 애니메이션 속성.
  • whileTap : 컴포넌트를 클릭하고 있는 동안의 애니메이션 속성
  • whileFocus : 컴포넌트를 클릭해 포커스된 동안의 애니메이션 속성
  • whileDrag : 끌기 제스쳐가 발생하는 동안의 애니메이션 속성
  • whileInView : 보통 스크롤 할 때 사용, 내리면서 컴포넌트가 뷰포트에 있는 동안의 애니메이션 속성

 

drag

끌기 활성화기본적으로 false로 설정됨양방향 = true 특정방향으로 = "x" or "y"

 

rbg값으로 해야 색이 천천히 변함

 

dragConstraint

허용된 드래그 가능 영역에 제약 조건 적용

 

1. 픽셀 이용

dragConstraints={{top:-200, bottom: 200, left: -200, right:200}}

2. ref이용

function App() {
  const biggerBoxRef = useRef<HTMLDivElement>(null)
  return (
    <Wrapper>
      <BiggerBox ref = {biggerBoxRef}>
        <Box 
          drag 
          dragConstraints={biggerBoxRef}/>
      </BiggerBox>
     </Wrapper> 
  );
}

 

dragSnapToOrigin

드래그 가능 요소를 드래그를 놓을 때, 원점으로 다시 애니메이션

 

dragElastic

외부 제약 조건에서 허용되는 이동 정도(당기는 힘의 정도)

0 = 움직임 없음

1 = 전체 움직임

(기본은 0.5)

 


Scroll

scroll하는 대로 커지고 작아지게 하는거 배우기 전에 MotionValues를 배워야함

MotionValues

function App() {
  const x = useMotionValue(0);
  const rotateZ = useTransform(x, [-800, 800], [-360, 360]);
  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))",
    ])
  return (
    <Wrapper style={{background: gradient}}>
        <Box 
          style = {{x, rotateZ}}
          drag = "x"
          dragSnapToOrigin/>
     </Wrapper> 
  );
}

 

useMotionValue

MotionValues는 애니메이션 값의 상태(state)와 속도(velocity)를 추적한다.

MotionValue는 React State가 아니기 때문에 Motion Value값이 바뀌어도 리랜더링이 일어나지 않는다

 

export function MyComponent() {
  const x = useMotionValue(0)
  return < motion.div style={{ x }} />
}

useMotionValue 후크로  MotionValues를 생성할 수 있다.

useMotionValue에 전달된 값은Motion Value의 초기 상태로 작동

 

x.set(100)

x.get() // 100

set으로 설정하고 get으로 값을 읽을 수 있음~!

 

useTransform

useTransform 훅을 통해 MotionValues를 연결한다
useTransform()는 한 값 범위에서 다른 값 범위로 매핑하여 다른 MotionValue의 output을 변환하는 MotionValue를 만든다
x(Motion Value)값을 다른 output값으로 변환해준다.
ex) x: -400 => 1

const input = [-200, 0, 200]
const output = [0, 1, 0]
const opacity = useTransform(x, input, output)

return < motion.div drag="x" style={{ x, opacity }} />

 

이제 scroll을 해보자~!

function App() {
  const x = useMotionValue(0);
  const rotateZ = useTransform(x, [-800, 800], [-360, 360]);
  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))",
    ])
    const {scrollYProgress} = useViewportScroll();
    const scale = useTransform(scrollYProgress, [0,1], [1,5])
  return (
    <Wrapper style={{background: gradient}}>
        <Box 
          style = {{x, rotateZ , scale}}
          drag = "x"
          dragSnapToOrigin/>
     </Wrapper> 
  );
}

 

useViewPortScroll

뷰포트가 스크롤될 때 업데이트 되는 MotionValues를 리턴해줌

 

scrollX: 실제 수평 스크롤 픽셀 ex) 500px
scrollY: 실제 수직 스크롤 픽셀 ex) 500px
scrollXProgress : 0 ~ 1 사이의 수평 스크롤
scrollYProgress : 0 ~ 1 사이의 수직 스크롤(가장 상단 0, 가장 하단 1)


SVG animation

https://fontawesome.com/

 

Font Awesome

The world’s most popular and easiest to use icon set just got an upgrade. More icons. More styles. More Options.

fontawesome.com

각종 로고 모여있는 사이트

 

const Svg = styled.svg`
  width: 300px;
  height: 300px;
  path {
    stroke:white;
    strokeWidth: 2;
  }
`;

const svgVar = {
  start: {
    pathLength: 0,
    fill: "rgba(255,255,255,0)"
  },
  end: {
    pathLength:1,
    fill: "rgba(255,255,255,1)",
    transition: {
      default : {duration : 3},
      fill : {duration : 1, delay: 1}
    }
  }
}

function App() {
  
  return (
    <Wrapper >
      <Svg xmlns="http://www.w3.org/2000/Svg" viewBox="0 0 448 512">
        <motion.path 
          variants={svgVar}
          initial = "start"
          animate = "end"
          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"/>
        </Svg>
      
     </Wrapper> 
  );
}

 

pathLength

0부터 1까지 값을 가짐

 

transition : default

transition: {
  default : {duration : 3},
  fill : {duration : 1, delay: 1}
}

transition 속성에서 defalut로 다른 모든 속성에 설정하고

!특정! 속성에만 다르게 설정할 수도 있음


AnimatePresence

AnimatePresence를 사용하면 React 트리에서 컴포넌트가 제거될 때 제거되는 컴포넌트에 애니메이션 효과를 줄 수 있다. React에는 수명 주기 메서드가 없기 때문에 종료 애니메이션(exit)을 활성화해야 한다

const boxVariants = {
  entry: (back: boolean) => ({
    x: back ? -200 : 200,
    opacity: 0,
    scale: 0,
  }),
  center: {
    x: 0,
    opacity: 1,
    scale: 1,
    transition: {
      duration: 1,
    },
  },
  exit: (back:boolean) => ({
    x: back ? 200 : -200, 
    opacity: 0, 
    scale: 0, 
    transition: {
      duration: 1 
    }
  })
};

function App() {
  const [visible, setVisible] = useState(1);
  const [back , setBack] = useState(false);
  const nextPlease = () => {
    setVisible(prev => prev === 10? 10 : prev + 1)
    setBack(false);
  };
  const prevPlease = () => {
    setVisible(prev => prev === 1 ? 1 : prev - 1)
    setBack(true);
  };
  return (
    <Wrapper>
      <AnimatePresence custom={back}>
        {[1,2,3,4,5,6,7,8,9,10].map(i => i === visible ? 
          <Box 
            custom={back}
            key = {visible}
            variants={boxVariants}
            initial = "entry" 
            animate="center"
            exit = "exit" >
              {visible}
          </Box> : null)}
      </AnimatePresence>
      <button onClick = {nextPlease}>NEXT</button>
      <button onClick = {prevPlease}>PREV</button>
     </Wrapper> 
  );
}

 

custom

각 애니메이션 컴포넌트에 대해 동적 variants를 다르게 적용할 때 사용할 수 있는 사용자 지정 데이터

const variants = {
  visible: (custom) => ({
  opacity: 1,
  transition: { delay: custom * 0.2 }
  })
}
  
<motion.div custom={0} animate="visible" variants={variants}/>
<motion.div custom={1} animate="visible" variants={variants}/>
<motion.div custom={2} animate="visible" variants={variants}/>

 

exitBeforeEnter

true로 설정되면 AnimatePresence는 한번에 하나의 컴포넌트만 랜더링 함

즉 entry 가 다 끝나면 exit이 실행됨


Layout Animation

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> 
  );
}

개쩐다...

layout

true인 경우 해당 컴포넌트의 레이아웃이 변경될 때 새 위치에 자동으로 애니메이션이 적용됨

<motin.div lyaout/>

 

layoutId

layoutid prop을 가진 모션 컴포넌트들 간에 애니메이션을 적용할 수 있음

layoutId가 있는 새 컴포넌트가 추가되고 다른 컴포넌트가 제거되면 이전 컴포넌트에서 새 컴포넌트로 레이아웃 애니메이션을 수행함

시각적으로 하나의 연속 컴포넌트로 처리됨!! 멋져

'React' 카테고리의 다른 글

[React] Recoil Selector의 get, set 사용법  (0) 2022.10.22
[모각코][React] useQuery  (0) 2022.08.07
[React] React Hook Form  (0) 2022.07.15
[React] Recoil로 상태 관리 하기  (0) 2022.07.14
[React] ThemeProvider  (0) 2022.07.09

관련글 더보기