[TIL]React/Typescript Framer-Motion

ohoho·2024년 10월 31일

슬기로운스터디

목록 보기
39/54

오늘 공부한것 & 기억할 내용

FramerMotion

  • 일반적인 HTML태그에 모션을 적용하려면 <motion.div> 형식으로 사용해야한다.
  • styled-component로 만든 태그는 const Box = styled(motion.div) 이렇게 사용
{/* initial은 초기값 지정 */}
<Box transition={
  {type:"spring", bounce:0.5}} initial={{scale:0}} animate={{scale:1, rotate:360}
}>
  Motion</Box>

variants

  • 객체로 만들어 애니메이션 사용 가능
const myVars = {
  start:{scale:0},
  end:{scale:1, rotate:360,transition:{type:"spring", bounce:0.}}
}

<Box variants={myVars} initial="start" animate="end">Motion</Box>

//==================================================================

const boxVars = {
  start:{
    opacity:0,
  },
  end:{
    opacity:1,
    transition: {
    type: "spring",
    //하위 컴포넌트들에게 0.5 딜레이를 해준다
    delayChildren:0.5,
    //하위 컴포넌트들에게 각각 딜레이 0.5s씩 더해주는것
    staggerChildren:0.5
  }
}
}
{/* 상위 컴포넌트가 variants를 가지고 있다면 하위 컴포넌트에게 initial, animation을 똑같이 주고 있는 상태다. */}
<Box variants={boxVars} initial="start" animate="end">
{/* 상위 컴포넌트에서 받아온 initial,animate작성은 생략 가능
하위 컴포넌트의 객체 속성값도 상위에서 받아오는 값이랑 동일하게 작성한다
*/}
<Circle variants={circleVars}/>
</Box>

//==================================================================

//biggerBoxref을 사용해 Box컴포넌트가 BiggerBox의 밖으로 나가지 않게 만든다.
const boxVars = {
  hover:{scale:1.5,rotate:90},
  tap:{scale:1,borderRadius:"100px"},
  drag:{backgroundColor:"rgb(46,204,113)",transition:{duration:0.8}
}
  
const biggerBoxref = useRef(null)
return(
  {/* while~ 마우스로 인해 발생되는 이벤트 
  whileDrag안의 backgroundColor를 사용할때는 rgb값을(숫자) 넣어야 애니메이션 효과 할 수 있다.
  */}
  <BiggerBox ref={biggerBoxref}>
    {/* dragSnapToOrigin은 drag해도 컴포넌트가 원래 위치로 돌아가게 만들어준다 */}
    <Box drag dragSnapToOrigin variants={boxVars} whileHover="hover" whileTap="tap" dragConstraints={biggerBoxref}  />
  </BiggerBox>
)

useMotionValue

  • useMotionValue는 특정한 값을 추적할 수 있도록 해준다.
//ReactJs에 영향을 주지 않기에 값이 바뀌어도 컴포넌트 리렌더링 되지 않는다.
const x = useMotionValue(0)
//갑이 변경된걸 실시간으로 확인하고 싶을때 사용
useMotionValueEvent(x,"change",(i) => {console.log('x',i)})


{/* 위치가 변경될때마다 변경된 값을 확인하기위해 선언한 x 를 style에 넣어준다*/}
<Box style={{x}} drag dragSnapToOrigin  />

useTransform

  • useMotionValue의 값을 mapping할 수 있다.
  • useMotionValue로부터 얻은 값을 기반으로 애니메이션을 조정하거나 스타일을 변경하고 싶을 때 사용
//원하는 좌표의 값들을 먼저 입력해주고, 좌표값마다 받아올 값을 뒤에 적어준다
//입력값과 출력값 개수가 같아야함
const scale = useTransform(x,[-800,0,800],[2,1,0.1])

{/* 위치가 변경될때마다 변경된 값을 확인하기위해 선언한 x 를 style에 넣어준다*/}
<Box style={{x,scale}} drag="x" dragSnapToOrigin  />
  
  
//Box의 (useMotionValue)x값을 사용해 Wrapper backround변경
const x = useMotionValue(0)
const gradient = useTransform(x,[-800,0,800],[
  "linear-gradient(135deg, rgb(0,210,238), rgb(0,83,238))",
  "linear-gradient(135deg, rgb(238,0,153), rgb(221,0,238))",
  "linear-gradient(135deg, rgb(0,238,155), rgb(238,178,0))"
])
<Wrapper style={{background:gradient}}>
   <Box style={{x,rotate}} drag="x" dragSnapToOrigin  />
</Wrapper>
  );

useScroll

  • 페이지 스크롤 애니메이션
//scrollY는 픽셀 scrollYProgress는 0 부터 1까지
const {scrollY,scrollYProgress} = useScroll()
useMotionValueEvent(scrollY,"change",(i) => {console.log('scrollY',i)})
useMotionValueEvent(scrollYProgress,"change",(i) => {console.log('scrollYProgress',i)})

//스크롤에 따른 scale변화주는 useTransform사용
const scale = useTransform(scrollYProgress,[0,1],[1,30])

transition

const svg = {
  start:{
    pathLength:0,
    fill:"rgba(255,255,255,0)"
  },
  end:{
    pathLength:1,
    fill:"rgba(255,255,255,1)",
  }
}
<Svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
  <motion.path
  variants={svg}
  initial="start" 
  animate="end"
  //property 각각 transition을 따로 줄 수 있다.
  //default는 모든 property에 적용되는값
  //여기서 default는 pathLength에 해당하는 duration
  transition={{
    default:{duration:5},
    fill:{duration:2,delay:2}
  }}
  stroke="white" strokeWidth="2" fill="transparent" d="..."/>
</Svg>

AnimatePresence

  • animate가 끝날때 효과를 줄 수 있다.
const boxVars = {
  start:{
    opacity:0,
    scale:0
  },
  end:{
    opacity:1,
    scale:1,
    rotate:360
  },
  leaving:{
    opacity:0,
    y:20
  }
}

const [show,setShow] = useState(false)
const toggleButton = () => setShow(pre => !pre)

<button onClick={toggleButton}>Click</button>
{/* AnimatePresence안에는 항상 조건문이 들어가 있어야 한다.
	exit는 animate가 사라질때 나타날 효과를 설정해준다
*/}
<AnimatePresence>
  {show ? <Box variants={boxVars} initial="start" animate="end" exit="leaving"/> : null}
</AnimatePresence>

AnimatePresence로 만든 슬라이드

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

function App() {
  const [visible, setVisible] = useState(1);
  const [isBack, setIsBack] = useState(false);
  const nextPlease = () => {
    setIsBack(false);
    setVisible((prev) => (prev === 10 ? 10 : prev + 1));
  };
  const prevPlease = () => {
    setIsBack(true);
    setVisible((prev) => (prev === 1 ? 1 : prev - 1));
  };
  return (
    <Wrapper>
    //custom사용시에는 AnimatePresence와 컴포넌트 둘 다 적어준다
      <AnimatePresence custom={isBack}>
        <Box
		  //custom을 사용하면 boxVariants에 property를 전달 할 수 있다.
          custom={isBack}
          variants={boxVariants}
          initial="entry"
          animate="center"
          exit="exit"
          key={visible}
        >
          {visible}
        </Box>
      </AnimatePresence>
      <button onClick={prevPlease}>Prev</button>
      <button onClick={nextPlease}>Next</button>
    </Wrapper>
  );
}

layout

const [click,setClick] = useState(false)
const toggleClick = () => setClick(per =>  !per)

<Box style={{
     justifyContent: click ? "center" : "flex-start",
     alignItems: click ? "center" : "flex-start"
     }}>
{/* 외부(상위컴포넌트)의 의해 css로 변화되는 컴포넌트는 layout를 사용하면 css가 animate효과를 갖게한다. */}
  <Circle layout/>
  </Box>

layoutId

  • 같은 id값을 가진 컴포넌트를 연결시켜주는 animation효과를준다
//click에의해 서로 보이고 안보여지는 효과를 layoutId를 사용해 같은 값을 넣어주면 animate효과로 해준다.
<Box>
  {!click ? <Circle layoutId="circle"/> : null}
</Box>
<Box>
  {click ? <Circle layoutId="circle"/> : null}
</Box>

layoutId를 사용한 팝업 애니메이션 만들기

  • layoutId를 사용하여 Grid안의 Box와 Overlay안의 Box layoutId값을 일치시켜 간단한 팝업을 구현
const Box = styled(motion.div)`
  background-color: rgba(255, 255, 255, 1);
  border-radius: 40px;
  height: 200px;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.06);
`;
const Overlay = styled(motion.div)`
  width: 100%;
  height: 100%;
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
`;
const Grid = styled.div`
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  width: 50vw;
  gap: 10px;
  div:first-child,
  div:last-child {
    grid-column: span 2;
  }
`;

const AniOverlay = {
  start:{backgroundColor:"rgba(0,0,0,0)"},
  end:{backgroundColor:"rgba(0,0,0,0.7)"},
  exit:{backgroundColor:"rgba(0,0,0,0)"}
}


<Grid>
  {[1,2,3,4].map(i => 
   <Box key={i} layoutId={i+""} onClick={() => setId(i+"")}>{i}</Box>)
	}
</Grid>
<AnimatePresence>
    {id ? 
     <Overlay onClick={() =>  setId(null)} 
     variants={AniOverlay} 
      initial="start" 
      animate="end" 
      exit="exit">
       <Box style={{width:400,height:200}} layoutId={id+""}>{id}</Box>
	</Overlay> 
    : null}
</AnimatePresence>

motion 공식문서

배운점 & 느낀점

여태까지 animation을 사용하기 위해선 css으로 작업을 해왔는데 FramerMotion을 배우면서 너무 간편하다고 생각했다. css으로 animation을 만들면 코드도 길어지고 노가다의 연속이었는데 라이브러리를 사용하니 너무 간편하고 좋은거같다. 잘만 사용하면 정말 간편하게 팝업들도 만들 수 있고 뚝뚝 끊어지는 정적인코드를 동적인코드로 만들 수 있는 부분이 좋은거같다.

0개의 댓글