[React] Framer-Motion으로 슬라이드 만들기, 버그 해결

게코젤리·2023년 2월 13일
0
import styled from 'styled-components';
import { AnimatePresence, motion } from 'framer-motion';
import { useState } from 'react';

const Wrapper = styled(motion.div)`
  height: 100vh;
  width: 100vw;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

const Box = styled(motion.div)`
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  top: 50px;
  font-size: 24px;
  width: 150px;
  height: 150px;
  background-color: rgba(255, 255, 255, 1);
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;

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

function App() {
  const [visible, setVisible] = useState(1);
  const [back, setBack] = useState(false);
  
  const slideNext = () => {
    setBack(false);
    setVisible((prev) => (prev === 5 ? 1 : prev + 1));
  };
  const slidePrev = () => {
    setBack(true);
    setVisible((prev) => (prev === 1 ? 5 : prev - 1));
  };
  return (
    <Wrapper>
      <AnimatePresence custom={ back }>
        <Box
          custom={ back }
          variants={boxVariants}
          initial="entry"
          animate="animate"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={slidePrev}>prev</button>
      <button onClick={slideNext}>next</button>
    </Wrapper>
  );
}

export default App;

슬라이드 만들기

  • 배열을 매핑하지 않아도 AnimatePresence의 자식 요소 key를 바꿔주면 쉽게 슬라이드를 만들 수 있다. (key의 변경 -> 기존 요소는 사라지고(exit) 새 요소가 생성(entry))

  • custom : variants에 데이터를 보낼 수 있게 해주는 프로퍼티. custom 사용을 위해서는 variants 속성(위의 예에선 entry, exit)의 형식을 object를 return 하는 함수로 바꿔준다.

문제점


prev버튼을 누르고 next를 눌렀을 때 첫 exit 애니메이션 동작이 반대로 실행되는 문제. setBack()이 비동기로 값을 바꾸기 때문에 back이 변경되기전에 애니메이션이 먼저 실행되기 때문.

해결

custom props 사용시 props(위 예에선 back)를 obj형태로 보낸다.

interface ICustomProps {
  back: boolean;
}

const boxVariants = {
  entry: ({ back }: ICustomProps) => ({
    x: back ? -500 : 500,
    opacity: 0,
    scale: 0,
  }),
  animate: {
    x: 0,
    opacity: 1,
    scale: 1,
    transition: {
      duration: 0.3,
    },
  },
  exit: ({ back }: ICustomProps) => ({
    x: back ? 500 : -500,
    opacity: 0,
    scale: 0,
    rotateX: 0,
    transition: {
      duration: 0.3,
    },
  }),
};

function App() {
  // 생략
  return (
    <Wrapper>
      <AnimatePresence custom={{back}}>
        <Box
          custom={{ back }}
          variants={boxVariants}
          initial="initial"
          animate="animate"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={slidePrev}>prev</button>
      <button onClick={slideNext}>next</button>
    </Wrapper>
  )
}

0개의 댓글