가수면

[Framer Motion] Framer Motion 본문

React/라이브러리

[Framer Motion] Framer Motion

니비앙 2023. 3. 28. 21:39

npm install framer-motion

애니메이션

 

scaleX: 가로 크기

transform-origin: 요소 변형의 원점을 설정

x: 가로 좌표

y: 세로 좌표

pathLength: 테두리 길이 (initial: 0, animate: 1로 설정해줄 경우 테두리가 서서히 그려짐)

transtion

type

ㄴspring: 통통 튕김(x 또는 scale은 spring이 자동 적용 됨)

ㄴtween: spring효과 없이 딱 멈춤(opacity 또는 color는 tween이 자동 적용 됨)

ㄴmass: 무게감

bounce: type: "spring"일 때 튕김 정도(탄력), 0 ~ 1 사이값

damping: 반대힘. 0으로 설정하면 스프링이 무한정 진동

duration: 시작부터 끝까지 실행되는 시간

delay : 시작되기까지 시간

 

애니메이션 반복 시키기

배열을 설정해주고 transition으로 repeat 횟수를 설정해주면 해당 배열값이 반복됨

const logoVariants = {
  normal: {
    fillOpacity: 1,
  },
  active: {
    fillOpacity: [0, 1, 0],
    transition: {
      repeat: Infinity,
    },
  },
};

 

Variants

자동완성 기능 활성화시킬 수 있음

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

// ...

const variants: Variants = {
start: {},
end: {}
}

기본 형식

줄줄이 props로 길게 늘어놓을 필요 없이 props를 선언해 뽑아쓸 수 있음

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

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

자식 컴포넌트

자식들의 애니메이션을 지정해줄 때 하나하나 지정해주지 않아도 됨

(※ initial과 animate의 이름이 같아야 함 )

const boxVariants = {
  start: {
// ...
  },
  end: {
// ...
      delayChildren: 0.5,
      staggerChildren: 0.2,
    },
  },
};

const circleVariants = {
  start: {
// ...
  },
  end: {
// ...
  },
};


      <Box variants={boxVariants} initial="start" animate="end">
        <Circle variants={circleVariants} />
        <Circle variants={circleVariants} />
        <Circle variants={circleVariants} />
        <Circle variants={circleVariants} />
      </Box>

커스텀

custom을 설정하면 매개변수로 받아 Variants를 사용할 수 있음

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

// ...

const [back, setBack] = useState(false);

// ...

      <AnimatePresence>
        <Box custom={back} variants={box} initial="entry" animate="center" exit="exit" key={visible}>
          {visible}
        </Box>
      </AnimatePresence>

 

 

Gestures

마우스 이벤트

만약  자식 컴포넌가 있다면 따로 지정해주지 않아도 자식 컴포넌트에 마우스 이벤트가 저절로 상속된다.

※ 드래그 시 색상을 rgb로 설정해주면 transition을 따로 설정해주지 않아도 약간의 duration이 적용된다.

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

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

x, y로 고정

// ...
      <Box
        drag="x"
// ...

드래그 영역 제한하기

function App() {
  const biggerBoxRef = useRef<HTMLDivElement>(null);
  return (
    <Wrapper>
      <BiggerBox ref={biggerBoxRef}>
        <Box
          drag
          // 드래그 놓았을 때 정가운데로
          dragSnapToOrigin
          // 마우스 저항도 0~1사이값
          dragElastic={0}
          // dragConstraints={{ top: number, bottom: number, left: number, right: number }}가 기본 형태.
          // 0, 0, 0, 0으로 설정하면 드래그를 놓았을 때 가운데로 이동한다.
          // useRef값을 넣어주면 좌표값이 자동으로 계산됨
          dragConstraints={biggerBoxRef}
          variants={boxVariants}
          whileHover="hover"
          whileTap="click"
        />
      </BiggerBox>
    </Wrapper>
  );
}

 

좌표값 다루기

useMotionValueEvent

사용 가능한 이벤트는 다음과 같다.

  • change
  • animationStart
  • animationComplete
  • animationCancel
function App() {
  const x = useMotionValue(0);
  
  useMotionValueEvent(x, "animationStart", () => {
    console.log("animation started on x")
  })
  
  useMotionValueEvent(x, "change", (latest) => {
    console.log("x changed to", latest)
  })
  
  return (
    <Wrapper>
      <Box style={{ x }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}

 

useTransform

좌표값을 임의의 다른 값으로 치환

function App() {
  const x = useMotionValue(0);
  const test = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);

  return (
    <Wrapper>
      <Box style={{ x, scale: test }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}

응용 (x값에 따른 그래디에이션 배경색)

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, scale }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}

 

useScroll

scrollX / Y: 픽셀 단위 값.
scrollX / YProgress : 0~1사이 값.(퍼센티지)

  const { scrollYProgress } = useScroll();

  useMotionValueEvent(scrollYProgress, "change", (latest) => {
    console.log(latest);
  });

좌표값에 따른 스타일 변경

// ...

  const { scrollYProgress } = useScroll();
  const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
  return (
    <Wrapper>
      <Box style={{ x, scale }} drag="x" dragSnapToOrigin />
    </Wrapper>
  );
}

스크롤 값을 연산하려면 .get()을 붙여주어야 함.

// top: scrollY + 100을 아래와 같이 변경

top: scrollY.get() + 100,

 

특정 속성 애니메이션 지정하기

ex) 테두리색이 채워진 후에 배경색을 채우고자 할 경우

          transition={{
            default: { duration: 5 },
            // scale이든 뭐든 상관없음
            fill: { duration: 1, delay: 3 },
          }}

 

AnimatePresence

사라질 때의 애니메이션을 지정 가능하도록 해주는 태그

 

기본

state에 따라 나타나고 사라지는 컴포넌트를 제어

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

// ...

      <AnimatePresence>
        {showing ? (
          <Box
            variants={boxVariants}
            initial="initial"
            animate="visible"
            exit="leaving"
          />
        ) : null}
      </AnimatePresence>

onExitComplete

애니메이션이 끝나면 실행될 함수

initial={false}

첫 시작 애니메이션 비활성화

      <AnimatePresence initial={false} onExitComplete={toggleNextSlice}>

// ...

      </AnimatePresence>

 

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 layout />
      </Box>
    </Wrapper>
  );
}

서로 다른 컴포넌트를 애니메이션으로 연결하고싶다면 layoutId를 지정해주면 됨

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

useAnimation

길어지는 애니메이션을 훅으로 뺄 수 있음.

예시) animate={{ scaleX: isSearch ? 1 : 0 }}에 useAnimation을 적용해 바꿔보기

const inputAnimation = useAnimation();

 const toggleSearch = () => {
    if (searchOpen) {
// variants를 설정해주었다면 inputAnimation.start("start") 식으로 해도 됨
      inputAnimation.start({
        scaleX: 0,
      });
    } else {
      inputAnimation.start({ scaleX: 1 });
    }
    setSearchOpen((prev) => !prev);
  };
  
// ...

 animate={inputAnimation}

'React > 라이브러리' 카테고리의 다른 글

react-markdown  (0) 2023.06.10
Tailwind CSS  (1) 2023.06.08
React beautiful dnd  (0) 2023.03.23
[React Query] 비동기 취소하기  (0) 2023.01.12
[React Query] 인증  (0) 2023.01.11
Comments