Framer Motion

zimablue·2023년 12월 2일
0

third-party

목록 보기
1/3

Framer Motion은 React 애플리케이션에서 애니메이션을 쉽게 추가할 수 있도록 도와주는 JavaScript 라이브러리입니다.


Installation

npm install framer-motion

Importing

import { motion } from "framer-motion";





The motion component


무언가를 애니메이트 할 때 motion으로 HTML element를 불려와야 합니다.


예시

<motion.div />

styled-components를 사용하여 motion component를 만들 수 있습니다.

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





animate


부분의 애니메이션은 motion 구성 요소와 animate prop으로 수행됩니다.

<Box animate={{ borderRadius: "100px" }} />





transition


transition prop에 기본 전환을 전달하여 다양한 종류의 애니메이션을 정의할 수 있습니다.
이 속성을 사용하면 요소가 변화할 때 애니메이션의 duration, easing, delay, repeat 등을 설정할 수 있습니다.
이러한 prop을 활용하여 요소들 사이의 부드럽고 자연스러운 애니메이션 효과를 만들어낼 수 있습니다.



delay

3초 후 애니메이션이 동작합니다.

<Box transition={{ delay: 3 }} animate={{ borderRadius: "100px" }} />



duration

완료 될때까지 3초가 소요됩니다.

<Box transition={{ duration: 3 }} animate={{ borderRadius: "100px" }} />



type

spring

spring은 애니메이션의 효과를 부드럽게 만들어주는 속성 중 하나입니다.
type의 기본값이며 요소의 움직임이 탄력적이고 자연스럽게 보이도록 설정할 수 있습니다.


spring transition 에서 사용되는 일부 주요한 속성있습니다.

  • Stiffness (강도)
    : 스프링의 강도를 나타냅니다. 값이 높을수록 스프링의 힘이 강해져 빠르게 움직입니다. 이 값이 높을수록 더 빠르게 변화하게 됩니다.

  • Damping (감쇠)
    : 스프링의 진동을 억제하는 비율을 나타냅니다. 값이 낮을수록 스프링의 진동이 더 오래 지속됩니다. 따라서 낮은 값일수록 더 많은 반동(bounce)이 생기게 됩니다.

  • Mass (질량)
    : 스프링의 질량을 나타냅니다. 질량이 클수록 느리게 움직이게 되어 더 많은 반동이 발생할 수 있습니다.

  • Bounce (반동)
    : 스프링 애니메이션에서 요소가 끝점에 도달했을 때의 반동 효과를 의미합니다. 이 값이 크면 끝에 도달했을 때의 반동이 더 강력하게 작용하며, 애니메이션이 끝나고 나서 추가적인 반동이 발생합니다.

<Box
  transition={{ type: "spring", bounce: 0.1 }}
  initial={{ scale: 0 }}
  animate={{ scale: 1, rotateZ: 360 }}
/>

tween

tween은 시작 지점에서 끝 지점까지의 값을 부드럽게 보간하여 애니메이션을 만들어내는 방식입니다.
이를 통해 요소가 부드럽게 변화하고, 시작과 끝 사이를 자연스럽게 이동합니다.

<Box
  transition={{ type: "tween" }}
  initial={{ scale: 0 }}
  animate={{ scale: 1, rotateZ: 360 }}
/>



Enter animations

initial

initial 은 요소의 초기 상태를 설정하는 데 사용됩니다.
이 속성을 사용하면 요소가 렌더링되었을 때 초기에 적용되는 애니메이션 또는 스타일을 지정할 수 있습니다.

<Box initial={{ scale: 0 }} animate={{ scale: 1 }} />



variants


여러 애니메이션 상태를 하나의 객체로 정의하여 재사용할 수 있도록 도와주는 기능입니다.


예시

variants 사용하지 않음

function App() {
  return (
    <Wrapper>
      <Box
        transition={{ type: "spring", bounce: 0.1 }}
        initial={{ scale: 0 }}
        animate={{ scale: 1, rotateZ: 360 }}
      />
    </Wrapper>
  );
}

variants 사용

const myVars = {
  start: { scale: 0 },
  end: { scale: 1, rotateZ: 360, transition: { type: "spring", bounce: 0.1 } },
};

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



Propagation

Propagation은 애니메이션 상태의 상속 및 덮어쓰기를 조절하는 기능을 의미합니다.
variants 객체 내의 하위 요소에 대한 설정을 상속하거나 덮어쓰는 방식으로 작동합니다.
상위 요소에서 정의한 애니메이션 상태를 하위 요소에서 상속받아 사용하거나, 필요한 경우 하위 요소에서 해당 상태를 재정의하여 덮어쓸 수 있습니다.


예시

Circle 컴포넌트의 initialanimate Enter animations을 사용하려고 합니다.

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

Propagation으로 인해 CircleBox와 같이 initial="start" animate="end"을 생략할 수 있습니다.
CirclecircleVariantsstartend를 각각 initialanimate에 적용합니다.

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



Orchestration

Orchestration은 여러 개의 애니메이션 속성들이 함께 동작할 때 그들 사이의 조정을 의미합니다.
이를 사용하여 여러 애니메이션 간의 타이밍, 순서, 그리고 동작 방식을 조절할 수 있습니다.

일반적으로 orchestration은 두 가지 값으로 정의됩니다:

  • Synchronize (동기화)
    : 애니메이션들이 동시에 시작하고 종료되도록 설정됩니다. 이 경우 애니메이션들이 함께 실행되며, 동일한 지속 시간과 시작 시간을 갖습니다.

  • Sequence (순차)
    : 애니메이션들이 순차적으로 발생하도록 설정됩니다. 이 경우 각 애니메이션이 이전 애니메이션이 종료된 후에 시작됩니다.


예시

BoxCircle이 동시에 나타납니다.

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

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

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

delayChildren을 사용하여 Box의 자식 요소를 0.5초 이후에 나타냅니다.

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

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

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

staggerChildren을 사용하여 Box의 자식 요소를 순서대로 각각 0.2초 차이를 두고 생성합니다.

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.2,
    },
  },
};

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

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





Gesture animations


Drag

요소를 Drag하여 이동시킬 때 애니메이션을 적용합니다.
사용자가 요소를 Drag할 때 해당 요소를 부드럽게 이동시키는 효과를 줄 수 있습니다.

<motion.div drag />

dragSnapToOrigin

드래그 애니메이션을 조절하는 속성 중 하나입니다.
이를 통해 요소가 드래그를 끝낼 때 원래 위치로 자연스럽게 스냅되도록 설정할 수 있습니다.


dragElastic

드래그 애니메이션을 제어하는 속성 중 하나입니다.
이를 통해 드래그 중에 요소에 탄성을 부여하여 드래그 동작의 물리적 특성을 조절할 수 있습니다.
0에서 1이 될수록 탄성이 적어집니다.


dragConstraints

드래그하는 요소의 움직임을 제한하는 데 사용되는 속성입니다.
이를 사용하여 요소가 드래그되는 영역을 제한하거나 특정한 방향으로만 드래그될 수 있도록 설정할 수 있습니다.


  • Top, Right, Bottom, Left
    : 요소가 드래그될 수 있는 영역의 상하좌우 경계를 정의합니다.
    이 경계 내에서만 드래그가 가능하도록 제한할 수 있습니다.
<motion.div drag dragConstraints={{top: -50, bottom: 50 left: -50, right: 50}} />

  • Axis
    : 드래그를 제한할 축을 설정합니다.
    "x"는 수평 방향으로만, "y"는 수직 방향으로만 드래그되도록 제한할 수 있습니다.
<motion.div drag="x" />

  • useRef
    : useRef로 특정 Element로 범위를 제한할 수 있습니다.
const boxVariants = {
  hover: { rotateZ: 90 },
  click: { borderRadius: "100px" },
};

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



Pan

Drag와 유사하지만 좀 더 자유로운 움직임을 허용하며, Drag가 아니라 요소를 사용자의 Pan 제스처에 맞춰 움직입니다.



Swipe

사용자가 화면을 Swipe할 때 요소에 애니메이션을 적용합니다.
예를 들어, Swipe 동작으로 요소를 서서히 사라지게 하거나 다른 방향으로 이동시키는 등의 효과를 줄 수 있습니다.



Tap

사용자가 요소를 Tap할 때 애니메이션을 발생시킵니다.
Tap 동작에 대한 반응으로 요소를 확대, 축소, 색상 변경 등의 변화를 줄 수 있습니다.



Animation helpers

whileHover, whileTap, whileFocus, whileDrag, whileInView와 같은 속성들은 제스처(Hover, Tap, Focus, Drag, View 내에 있을 때 등) 동안 특정한 애니메이션을 정의하는 데 사용됩니다.

  • whileHover
    : 요소가 호버(마우스가 요소 위에 올라갔을 때) 상태일 때 애니메이션을 적용합니다.
    주로 호버 상태일 때 요소의 크기를 변경하거나 색상을 변화시키는 등의 효과를 줄 때 사용됩니다.

  • whileTap
    : 요소가 탭(클릭) 상태일 때 애니메이션을 적용합니다.
    주로 요소가 탭되었을 때 크기를 축소하거나 이동시키는 등의 효과를 줄 때 활용됩니다.

  • whileFocus
    : 요소가 포커스를 받았을 때 애니메이션을 적용합니다. 특히 입력 필드와 같은 요소가 포커스를 받았을 때 테두리 색상 변경이나 크기 변화 등의 효과를 줄 때 사용됩니다.

  • whileDrag
    : 요소가 드래그 상태일 때 애니메이션을 적용합니다. 요소가 드래그되는 동안 위치를 변경하거나 투명도를 조절하는 등의 효과를 줄 때 사용됩니다.

  • whileInView
    : 요소가 화면 안에 보일 때의 상태를 나타내며, 화면에 들어왔을 때 또는 나갔을 때의 애니메이션을 정의할 수 있습니다.


예시

const boxVariants = {
  hover: { scale: 1.5, rotateZ: 90 },
  click: { scale: 1, borderRadius: "100px" },
  drag: { backgroundColor: "rgb(46, 204, 113)", transition: { duration: 1 } },
};

function App() {
  return (
    <Wrapper>
      <Box
        drag
        variants={boxVariants}
        whileHover="hover"
        whileDrag="drag"
        whileTap="click"
      />
    </Wrapper>
  );
}





MotionValues

useMotionValue()

useMotionValue 훅은 React 함수 컴포넌트 내에서 사용되며, 단일한 애니메이션 값을 생성하고 추적하는 데 사용됩니다.

import { motion, useMotionValue } from "framer-motion";

function App() {
  const x = useMotionValue(0);

  return (
    <Wrapper>
      <button onClick={() => x.set(200)}>click me</button>
      <Box style={{ x }} />
    </Wrapper>
  );
}



useMotionValueEvent()

useMotionValueEvent훅은 애니메이션 값이 변경될 때 트리거할 수 있는 이벤트를 생성합니다.

import { motion, useMotionValue, useMotionValueEvent } from "framer-motion";

function App() {
  const x = useMotionValue(0);
  useMotionValueEvent(x, "change", (l) => {
    console.log(l); // 200
  });

  return (
    <Wrapper>
      <button onClick={() => x.set(200)}>click me</button>
      <Box style={{ x }} />
    </Wrapper>
  );
}





useTransform


한 범위의 모션 값(애니메이션 값)을 다른 범위의 값으로 매핑하거나 변환하는 데 사용됩니다.

import { motion, useMotionValue, useTransform } from "framer-motion";

const x = useMotionValue(1)
const y = useMotionValue(1)
 
const z = useTransform(() => x.get() + y.get()) // 2



Mapping

useTransform(모션 값, 입력 범위, 출력 범위)

예시

Box의 x좌표가 -800일 때 크기는 2배가 되고, 0일 때 1배가 되고, 800일 때 0.1배가 됩니다.

function App() {
  const x = useMotionValue(0);
  const scale = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);
  return (
    <Wrapper>
      <Box style={{ x, scale }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}





useScroll


useScroll 훅은 스크롤 이벤트를 감지하고 컴포넌트의 상태를 변경하는 데 사용됩니다.
이 훅을 사용하면 페이지의 스크롤 위치에 따라 애니메이션을 트리거하거나 UI를 변경하는 등 다양한 기능을 구현할 수 있습니다.

  • scrollX / Y
    : 픽셀 단위로 정확한 스크롤 위치를 나타내는 값입니다.
    수평 방향(scrollX)과 수직 방향(scrollY)의 스크롤 위치를 각각 얻을 수 있습니다.

  • scrollXProgress / YProgress
    : 정의된 오프셋(일반적으로 요소의 시작과 끝) 사이의 스크롤 위치를 0부터 1까지의 값으로 표현한 것입니다.
    예를 들어, 요소의 시작부터 끝까지의 스크롤 위치를 0에서 1로 변환하여 나타내줍니다.

import { useScroll } from "framer-motion"

const { scrollX, scrollY, scrollXProgress, scrollYProgress } = useScroll()

예시

스크롤로 페이지를 내렸을 때 scrollY의 결과입니다.
스크롤 위치를 픽셀 단위로 볼 수 있습니다.

const { scrollY } = useScroll()

useMotionValueEvent(scrollY, "change", (latest) => {
  console.log("Page scroll: ", latest)
})


스크롤로 페이지를 내렸을 때 scrollYProgress의 결과입니다.
스크롤을 아래로 내릴수록 1에 가까워집니다.

const { scrollYProgress } = useScroll()

useMotionValueEvent(scrollYProgress, "change", (latest) => {
  console.log("Page scroll: ", latest)
})


useTransform과 함께 사용한 결과입니다.

function App() {
  const x = useMotionValue(0);
  const { scrollYProgress } = useScroll();
  
  // x좌표에 따라 요소를 2D 평면에서 z축을 중심으로 회전시킵니다
  const rotateZ = useTransform(x, [-800, 800], [-360, 360]);
  
  // x좌표에 따라 색깔이 변합니다.
  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))",
    ]
  );

  // 스크롤이 내려갈 수록 scale은 5배에 가까워집니다.
  const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
  
  return (
    <Wrapper style={{ background: gradient }}>
      <Box style={{ x, rotateZ, scale }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}





SVG Animation


SVG를 가져오려면 아래와 같은 코드를 사용합니다.

<svg 
  xmlns="http://www.w3.org/2000/svg" 
  height="16" 
  width="14" 
  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"
    ></path>
</svg>

SVG도 motion을 통해 애니메이트 할 수 있습니다.



fill

SVG는 path를 가지고 있고 pathfill을 가지고 있습니다.
fill을 통해 색깔을 바꿀 수 있으며 fill: currentColor은 는 특정 요소의 색상을 현재 적용된 텍스트 또는 요소의 색과 일치하도록 설정하는 CSS 속성입니다.
즉, pathcolor를 가질 것이라는 의미입니다.



stroke

stroke는 도형의 윤곽선을 말합니다.
pathLength 속성은 요소의 실제 길이를 조정할 수 있습니다.
1은 완성된 길이이며 0이 되면 사라지게 됩니다.
이를 활용하여 윤곽선이 그려지는 듯한 애니메이션을 구현할 수 있습니다.


예시

stroke를 통해 윤곽선을 그린 후 fill을 통해 배경을 채우는 효과를 줄 수 있습니다.

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

const svg = {
  start: { pathLength: 0, fill: "rgba(255, 255, 255, 0)" },
  end: {
    fill: "rgba(255, 255, 255, 1)",
    pathLength: 1,
  },
};

function App() {
  return (
    <Wrapper>
      <Svg
        focusable="false"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 448 512"
      >
        <motion.path
          variants={svg}
          initial="start"
          animate="end"
          transition={{
            default: { duration: 5 },
            fill: { duration: 3, delay: 3 },
          }}
          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>
      </Svg>
    </Wrapper>
  );
}





AnimatePresence


요소가 마운트(mount)되거나 언마운트(unmount)될 때 애니메이션을 적용하는 데 사용됩니다.
주로 요소의 입장과 퇴장에 따른 애니메이션 효과를 제어할 때 활용됩니다.
AnimatePresence는 직계 자식요소가 React 트리에서 제거되는 시기를 감지하여 작동합니다.


예시

const boxVariants = {
  initial: {
    opacity: 0,
    scale: 0,
  },
  visible: {
    opacity: 1,
    scale: 1,
    rotateZ: 360,
  },
  leaving: {
    opacity: 0,
    scale: 0,
    y: 50,
  },
};

function App() {
  const [showing, setShowing] = useState(false);
  const toggleShowing = () => setShowing((prev) => !prev);

  return (
    <Wrapper>
      <button onClick={toggleShowing}>Click</button>
      <AnimatePresence>
        // 조건을 사용하여 Box를 마운트 하거나 언마운트
        {showing ? (
          <Box
            variants={boxVariants}
            initial="initial"
            // Box가 마운트될 때
            animate="visible"
            // Box가 언마운트될 때
            exit="leaving"
          />
        ) : null}
      </AnimatePresence>
    </Wrapper>
  );
}



AnimatePresence을 사용하면 carousel도 비교적 간단하게 구현할 수 있습니다.
단일 자식 key를 변경하면 슬 라이드쇼(슬라이더)와 같은 컴포넌트를 쉽게 만들 수 있습니다.

예시

React에서 key는 고유하기 때문에 만약 component의 key가 바뀌면 새로운 걸 re-render 합니다.
따라서 이전 component는 없어지고 AnimatePresence가 exit animation을 실행합니다.

import styled from "styled-components";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";

const boxVariants = {
  invisible: {
    x: 500,
    opacity: 0,
    scale: 0,
  },
  visible: {
    x: 0,
    opacity: 1,
    scale: 1,
    transition: {
      duration: 1,
    },
  },
  exit: { x: -500, opacity: 0, scale: 0, transition: { duration: 1 } },
};

function App() {
  const [visible, setVisible] = useState(1);
  const nextPlease = () => setVisible((prev) => (prev === 10 ? 1 : prev + 1));
  const prevPlease = () => setVisible((prev) => (prev === 1 ? 10 : prev - 1));
  return (
    <Wrapper>
      <AnimatePresence>
        <Box
          variants={boxVariants}
          initial="invisible"
          animate="visible"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={nextPlease}>next</button>
      <button onClick={prevPlease}>prev</button>
    </Wrapper>
  );
}



custom

custom을 사용하려면 variant의 object를 함수로 바꿔야 합니다.

custom 속성으로 isBack 상태를 variants 로 전달하여 isBack 상태에 따라 애니메이션을 변경합니다.

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

function App() {
  const [visible, setVisible] = useState(1);
  const [isBack, setIsBack] = useState(false);

  const nextPlease = () => {
    setIsBack(false);
    setVisible((prev) => (prev === 10 ? 1 : prev + 1));
  };
  const prevPlease = () => {
    setIsBack(true);
    setVisible((prev) => (prev === 1 ? 10 : prev - 1));
  };
  return (
    <Wrapper>
      <AnimatePresence custom={isBack}>
        <Box
          custom={isBack}
          variants={boxVariants}
          initial="entry"
          animate="center"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={nextPlease}>next</button>
      <button onClick={prevPlease}>prev</button>
    </Wrapper>
  );
}

mode

위의 예시에서는 사라질 Box의 exit animation와 나타날 Box의 enter animation이 동시에 실행됩니다.

만약 exit animation 이후 enter animation를 실행하고 싶다면 AnimatePresencemode={wait} 속성을 사용하면 해결됩니다.

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

function App() {
  const [visible, setVisible] = useState(1);
  const [isBack, setIsBack] = useState(false);

  const nextPlease = () => {
    setIsBack(false);
    setVisible((prev) => (prev === 10 ? 1 : prev + 1));
  };
  const prevPlease = () => {
    setIsBack(true);
    setVisible((prev) => (prev === 1 ? 10 : prev - 1));
  };
  return (
    <Wrapper>
      <AnimatePresence custom={isBack} mode={"wait"}>
        <Box
          custom={isBack}
          variants={boxVariants}
          initial="entry"
          animate="center"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={nextPlease}>next</button>
      <button onClick={prevPlease}>prev</button>
    </Wrapper>
  );
}



initial

initial 속성은 초기 상태를 정의하는 데 사용됩니다.
initial={false}로 설정하면 자식 요소가 처음 렌더링될 때 애니메이션을 사용하지 않습니다.



onExitComplete

onExitComplete 프로퍼티는 요소가 애니메이션을 통해 렌더링에서 제거된 후 에 호출되는 콜백 함수를 설정하는 데 사용됩니다.

function Home() {
  const [index, setIndex] = useState(0);
  const [leaving, setLeaving] = useState(false);
  const { data, isLoading } = useQuery<IGetMoviesResult>(
    ["movies", "nowPlaying"],
    getMovies
  );

  const increaseIndex = () => {
    if (leaving) return;
    toggleLeaving();
    setIndex((prev) => prev + 1);
  };
  const toggleLeaving = () => setLeaving((prev) => !prev);

  return (
    <Wrapper>
      {isLoading ? (
        <Loader>Loading...</Loader>
      ) : (
        <>
          <Banner bgPhoto={makeImagePath(data?.results[0].backdrop_path || "")}>
            <Title>{data?.results[0].title}</Title>
            <Overview>{data?.results[0].overview}</Overview>
          </Banner>
          <Slider>
            // 요소가 `unmount`될 때 toggleLeaving 실행
            <AnimatePresence initial={false} onExitComplete={toggleLeaving}>
              <Row
                variants={rowVariants}
                initial="hidden"
                animate="visible"
                exit="exit"
                transition={{ type: "tween", duration: 1 }}
                key={index}
              >
                {[1, 2, 3, 4, 5, 6].map((i) => (
                  <Box key={i}>{i}</Box>
                ))}
              </Row>
            </AnimatePresence>
          </Slider>
        </>
      )}
    </Wrapper>
  );





layout


레이아웃 변화가 발생할 때 요소가 부드럽게 애니메이션되도록 하는 역할을 합니다.
layout을 적용하면 리렌더링으로 인해 발생하는 모든 레이아웃 변경이 애니메이션화됩니다.


예시

layout을 적용하지 않는다면 레이아웃은 위와 같이 변경됩니다.

function App() {
  const [clicked, setClicked] = useState(false);
  const toggleClicked = () => setClicked((prev) => !prev);
  return (
    <Wrapper onClick={toggleClicked}>
      <Box
        style={{
          justifyContent: clicked ? "center" : "flex-start",
          alignItems: clicked ? "center" : "flex-start",
        }}
      >
        <Circle />
      </Box>
    </Wrapper>
  );
}

Circlelayout을 적용하면 레이아웃 변경이 애니메이션화됩니다.

function App() {
  const [clicked, setClicked] = useState(false);
  const toggleClicked = () => setClicked((prev) => !prev);
  return (
    <Wrapper onClick={toggleClicked}>
      <Box
        style={{
          justifyContent: clicked ? "center" : "flex-start",
          alignItems: clicked ? "center" : "flex-start",
        }}
      >
        <Circle layout />
      </Box>
    </Wrapper>
  );
}



layoutId

요소들 간의 레이아웃 전환을 부드럽게 처리하는 데 사용됩니다.
이를 통해 요소의 위치, 크기, 애니메이션 등의 변경 사항이 일어날 때, 두 요소 간의 전환을 부드럽게 처리하여 사용자 경험을 향상시킬 수 있습니다.

layoutId를 사용하여 요소들 사이에 고유한 식별자를 지정할 수 있습니다.
이 식별자는 요소들 간에 레이아웃이 변할 때(예: 페이지 전환, 리스트 아이템 변경 등) 같은 layoutId를 가진 요소들이 서로 부드럽게 전환될 수 있도록 해줍니다.


예시

layoutId를 사용하지 않는다면 레이아웃은 위와 같이 변경됩니다.

function App() {
  const [clicked, setClicked] = useState(false);
  const toggleClicked = () => setClicked((prev) => !prev);
  return (
    <Wrapper onClick={toggleClicked}>
      <Box>{!clicked ? <Circle style={{ borderRadius: 50 }} /> : null}</Box>
      <Box>
        {clicked ? <Circle style={{ borderRadius: 0, scale: 2 }} /> : null}
      </Box>
    </Wrapper>
  );
}

layoutId를 가진 Circle이 서로 부드럽게 전환될 수 있도록 해줍니다.

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





출처:
Framer Motion
노마드 코더

0개의 댓글

관련 채용 정보