2. Framer Motion 응용을 위한 개념 (layout, AnimatePresence)

금교영·2022년 4월 9일
22

framer motion

목록 보기
2/2
post-thumbnail

👷‍♂️ Framer Motion에 친숙하지 않다면 이 포스트를 읽어주세요

목표

본 글에서는 Framer Motion으로 아래와 같은 Tab 컴포넌트를 구현하기위해 알아야할 개념을 다룬다.

Layout Props

Layout props를 true로 설정하면 컴포넌트의 layout이 변할 때 그 모습을 부드럽게 이어준다. layout 이 변하는 경우를 다음과 같다.

  • 리스트의 순서가 바뀌었을 때
  • widthposition이 변했을 때
  • 부모의 레이아웃이 변할 때 ( flex 혹은 grid 로 변할 때)
  • 이외의 컴포넌트의 레이아웃이 변할 때

layout props를 이용하면 다음과 같은 컴포넌트를 만들 수 있다. 우선 체험해보고 자세히 알아보자.

⭐레이아웃 기능이 특별한 이유⭐

export default function App() {
  const [isOn, setIsOn] = useState(false);

  const toggleSwitch = () => setIsOn(!isOn);

  return (
    <div className="switch" data-isOn={isOn} onClick={toggleSwitch}>
      <motion.div className="handle"  transition={spring} />
    </div>
  );
}

const spring = {
  type: "spring",
  stiffness: 700,
  damping: 30,
};

예시로 살펴본 버튼은 이런 동작 원리는 다음과 같다. 사용자가 토글버튼을 클릭하면 data-isOn 의 값이 변하면서 css의 justify-content부드럽게 변한다. (css파일에서 data-isOn에 따라 스타일을 다르게 하는 코드가 있다.)

여기서 핵심은 ”부드럽게” 변하는 것이다. 본래 justify-content 속성은 transition과 animation에서 사용할 수 없다! transition이 가능한 속성 목록 에서 transition 과 animation에서 사용할 수 있는 속성을 볼 수 있다. 하지만 justify-content는 찾을 수 없다.

토글 버튼에 layout props를 삭제하고 css에서 transition을 주었다. 이해를 돕기위해 클릭시에 background-color를 변경시켰다. 이제 다시 클릭해보자.

배경 색깔에는 transition이 적용되지만 justify-content에서는 적용되지 않았다.

🚧 Framer-Motion에 따르면 이런 레이아웃의 변화를 transform 속성을 이용해서 처리했다고 한다. 본래의 속성을 이용해서 변화시키지 않기때문에 자식 컴포넌트들을 왜곡시킬 수 있다. 왜곡이 일어나는 경우에는 첫 번째 자식컴포넌트에도 layout props를 true로 설정해주는 것을 추천한다.

LayoutId

{items.map(item => (
   <motion.li layout>
      {item.name}
      {item.isSelected && <motion.div layoutId="underline" />}
			{//item.isSelected가 true라면 <motion.div layoutId="underline"/> 컴포넌트가 랜더링된다.}
   </motion.li>
))}

탭UI

다음은 우리가 만들 목표 컴포넌트의 일부이다. 아이템을 클릭하면 selectedtrue가 되는 Tab 아이템이 있다. true일 때는 밑줄 모양의 divElement가 랜더링된다.

다른 아이템을 선택하면 그 아이템에 밑줄이 랜더링된다. 여기서 layoutId 를 주목하자. 같은 layoutId를 가진 컴포넌트가 언마운트 되는 동시에 같은 layoutId를 가지는 컴포넌트가 새롭게 생긴다면 Framer Motion은 컴포넌트가 사라진 것처럼 보여주지 않고 애니메이션으로 부드럽게 이어준다. layoutId가 없거나 같지 않다면 뚝뚝 끊기는 모습을 보인다.

layoutId 기능을 키고 끌 수 있게 변형시켰다. 토글을 눌러보면서 차이를 확인하자. 기능을 끄고 탭 아이템들을 클릭하면 밑줄이 뚝뚝 끊기는 모습이 보인다. 기능을 키면 부드럽게 움직이는 모습을 볼 수 있다.

AnimatePresence

바로 위 예시에서 탭 아이템들을 클릭해보자. 이번에는 클릭하면 변하는 음식들을 유심히 보자. 음식이 변할 때 언마운트되는 음식이 살짝 올라가는 애니메이션을 볼 수 있다. Framer Motion의 AnimatePresence컴포넌트는 컴포넌트가 언마운트될 때 exit animation을 가능하게 해준다.

exit animationmotion 컴포넌트의 exit props에 애니메이션 상태를 전달하는 방식으로 결정된다. 이는 animate, initial과 같은 방식이다. 다음은 이미지 슬라이더를 만들 때 사용할 수 있는 코드다.

export const Slideshow = ({ image }) => (
  <AnimatePresence>
    <motion.img
      key={image.src}
      src={image.src}
      initial={{ x: 300, opacity: 0 }}
      animate={{ x: 0, opacity: 1 }}
      exit={{ x: -300, opacity: 0 }}
    />
  </AnimatePresence>
)

자식 컴포넌트들은 유일한 key를 가져야만 AnimatePresence가 제대로 동작한다. React는 리액트는 key가 다른 컴포넌트를 아예 다른 컴포넌트로 생각하고 동작한다. 이런 이유 때문에 다음에 올 컴포넌트가 마운트되는 동시에 이전 컴포넌트의 exit animation 를 실행시키게 해준다.

이미지 슬라이더를 보면 사진이 사라지기 전에 다음 사진이 미리 마운트되는 모습을 볼 수 있다.

변하는 음식 컴포넌트는 이런 방식으로 구현되었다.

<main style={{ position: "relative" }}>
          <ToggleBtn isLayout={isLayout} setIsLayout={setIsLayout} />
          <AnimatePresence exitBeforeEnter>
				 {//true 설정시 exitBeforeEnter는 새로운 컴포넌트가 나타나직 전에 이전 컴포넌트가 사라진다.}
            <motion.div
              key={selectedTab ? selectedTab.label : "empty"}
              animate={{ opacity: 1, y: 0 }}
              initial={{ opacity: 0, y: 20 }}
              exit={{ opacity: 0, y: -20 }} //올라가면서 흐려지는 애니메이션 
              transition={{ duration: 0.15 }}
            >
              {selectedTab ? selectedTab.icon : "😋"}
            </motion.div>
          </AnimatePresence>
        </main>

정리

layout, AnimatePresence는 vanila로 구현하기엔 다소 까다로운 UI를 쉽게 구현할 수 있게 해준다. 이 둘을 가지고 만들 수 있는 UI는 정말 다양하다. 쓸데없이 멋지기만 한 애니메이션이 아니라 사소하지만 사용자 경험을 풍부하게 하는 애니메이션을 구현할 수 있다.

개인적으로 페이지 전환 애니메이션을 좋아하는데 이를AnimatePresence로 구현할 수 있다. 다음 포스트에서는 이를 React, Next에서 어떻게 구현할 수 있는지 구체적인 코드를 가지고 이야기해보겠다.

🎈 참고 자료

profile
SW Engineer를 꿈꾸는 👨‍🌾

1개의 댓글

comment-user-thumbnail
2022년 10월 11일

도움이 많이 됐습니당!

답글 달기