react framer-motion

해적왕·2025년 4월 21일

overview

<motion.div animate={{ opacity: 1 }} />

브라우저가 기본적으로 애니메이션하지 못하는 값들까지 포함하여 모든 CSS 값을 애니메이션

숫자: 0, 100
숫자를 포함한 문자열: "0vh", "10px"
색상: Hex, RGBA, HSLA
display: "none" / "block"
visibility: "hidden" / "visible"
여러 숫자 및/또는 색상을 포함하는 문자열 (예: box-shadow)

value

일반적으로, 같은 타입의 값끼리만 애니메이션
"0px" → "100px"은 가능하지만 "0px" → "50%"는 불가능

x, y, width, height, top, left, right, bottom은 다른 단위 간에도 애니메이션이 가능

<motion.div
  initial={{ x: "100%" }}
  animate={{ x: "calc(100vw - 50%)" }}
/>
<motion.div
  initial={{ height: 0 }}
  animate={{ height: "auto" }}
/>

🔔
display 값을 "none"에서 "block"으로 바꾸는 애니메이션을 사용할 경우, display: none 상태의 요소는 크기를 측정할 수 없기 때문에 대신 visibility: "hidden"을 사용

Transform

CSS와는 달리, Motion은 각각의 transform 축을 독립적으로 애니메이션

이동(Translate): x, y, z
확대/축소(Scale): scale, scaleX, scaleY
회전(Rotate): rotate, rotateX, rotateY, rotateZ
기울이기(Skew): skew, skewX, skewY
원근(Perspective): transformPerspective

<motion.button
  whileHover={{ scale: 1.1 }}     // 마우스 올라갔을 때 커짐
  whileTap={{ scale: 0.9 }}       // 누르고 있을 때 작아짐
>
</motion.button>

framer-motion은 transform 속성을 직접 설정함으로써 하드웨어 가속도 지원

<motion.li
  initial={{ transform: "translateX(-100px)" }}
  animate={{ transform: "translateX(0px)" }}
  transition={{ type: "spring" }}
/>

📌 SVG 참고사항:
SVG 컴포넌트에서는 x, y 속성을 attrX, attrY로 설정

Transform origin

originX,originY,originZ

originXoriginY를 숫자로 설정하면 0에서 1 사이의 비율 값으로 인식
originZ는 픽셀 단위로 적용

// transform-origin의 x축 기준점을 요소의 가운데(50%)로 설정
<motion.div style={{ originX: 0.5 }} />

CSS variables

CSS 변수의 값을 애니메이션할 수 있고 CSS 변수를 애니메이션의 목표값으로도 사용

CSS 변수에 애니메이션 적용

많은 자식 요소에 동일한 애니메이션을 적용할 때 편리

CSS 변수 값을 애니메이션하면 항상 페인트 단계가 발생하므로
MotionValue를 사용하는 것이 성능 면에서 더 좋을 수 있음

<motion.ul
  initial={{ '--rotate': '0deg' }}
  animate={{ '--rotate': '360deg' }}
  transition={{ duration: 2, repeat: Infinity }}
>
  <li style={{ transform: 'rotate(var(--rotate))' }} />
  <li style={{ transform: 'rotate(var(--rotate))' }} />
  <li style={{ transform: 'rotate(var(--rotate))' }} />
</motion.ul>

CSS 변수를 애니메이션 목표로 사용

<motion.li animate={{ backgroundColor: "var(--action-bg)" }} />

SVG 그리기

pathLength 선이 얼마나 그려졌는지를 0 ~ 1 사이로 지정
pathSpacing 선 사이의 간격 조절 (대시처럼 효과 줄 때 사용)
pathOffset 선이 시작되는 위치를 조절

// pathLength: 0 → 선이 전혀 보이지 않음 (시작점)
// pathLength: 1 → 선이 완전히 다 그려진 상태
<motion.path initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} />

📌 참고
pathLength, pathSpacing, pathOffset은 Framer Motion이 SVG 내부에서 stroke-dasharray, stroke-dashoffset을 자동으로 계산해줘서 따로 계산할 필요 없음

stroke, fill, strokeWidth 같은 SVG 속성도 style이나 animate로 제어 가능

Transitions

x, scale처럼 물리적인 속성은 spring physics 기반으로 애니메이션
opacity, color 같은 속성은 시간 기반의 이징 곡선(duration-based easing curves)으로 처리

하지만 transition 속성을 사용해서 직접 애니메이션을 정의 가능

<motion.div
  animate={{ x: 100 }}
  transition={{ ease: "easeOut", duration: 2 }}
/>

Enter animations

초기 렌더링된 값과 animate 속성에 설정된 값이 다르면 자동으로 애니메이션이 실행
초기 값은 CSS로 설정하거나 아니면 initial prop을 통해 명시적으로 지정

<motion.li
  initial={{ opacity: 0, scale: 0 }}
  animate={{ opacity: 1, scale: 1 }}
/>

initial={false}로 설정하면 진입 enter animation을 비활성화
이렇게 하면 컴포넌트는 animate에 정의된 값으로 바로 렌더링

<motion.div initial={false} animate={{ y: 100 }} />

위 코드는 컴포넌트가 처음 나타날 때 y: 100 위치에 바로 렌더링
별도의 초기 위치에서 애니메이션되는 효과 x

Exit animations

일반적으로 React에서는 컴포넌트가 제거되면 즉시 DOM에서 사라지지만
Framer Motion의 AnimatePresence 컴포넌트를 사용하면
요소가 퇴장 애니메이션을 수행하는 동안 DOM에 남아 있도록 함

<AnimatePresence>
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

Keyframes

각 값을 순서대로 거치며 애니메이션이 진행

// 0 → 100 → 50 → 200 순서대로 진행
<motion.div animate={{ x: [0, 100, 50, 200] }} />

<motion.div
  animate={{
    scale: [1, 2, 2, 1, 1],
    rotate: [0, 0, 180, 180, 0],
    borderRadius: ["0%", "0%", "50%", "50%", "0%"],
  }}
  transition={{
    duration: 2,
    ease: "easeInOut",
    times: [0, 0.2, 0.5, 0.8, 1],
    repeat: Infinity,
    repeatDelay: 1,
  }}
/>

키프레임 애니메이션에서 현재 상태를 초기값으로 사용하려면 해당 위치를 null로 설정

<motion.div animate={{ x: [null, 100, 0] }} />

기본적으로 각 키프레임은 애니메이션 전체 시간 안에서 균일하게 배분되지만,
transition 속성의 times 옵션을 사용하면 각 키프레임의 타이밍을 직접 지정

<motion.circle
  cx={500}
  animate={{
    cx: [null, 100, 200],
    transition: { duration: 3, times: [0, 0.2, 1] }
  }}
/>

times는 0에서 1 사이의 진행 값 배열이며 각 키프레임이 애니메이션 전체에서 어느 시점에 위치할지를 정의
CX는 원의 중심 x좌표

Gesture animations

제스처가 시작되거나 끝날 때 목표 값으로 애니메이션을 쉽게 설정할 수 있는 속성을 제공
whileHover 마우스를 올리면 동작
whileTap 버튼 클릭 시 동작
whileInView 화면에 보이면 동작
whileFocus 포커스를 받을 때 (ex: 키보드)
whileDrag 드래그 중일 때

직접 값 지원

<motion.button
  whileHover={{
    scale: 1.2,
    transition: { duration: 1 }, // 마우스를 올리면 1.2
  }}
  whileTap={{ scale: 0.9 }} // 마우스를 클릭하면 0.9
/>

variant 이름으로 지정

<motion.button
  whileTap="tap"
  whileHover="hover"
  variants={buttonVariants}
>
  <svg>
    <motion.path variants={iconVariants} />
  </svg>
</motion.button>

hover

마우스 포인터가 컴포넌트 위에 올라가거나 벗어날 때를 감지
기존의 onMouseEnter, onMouseLeave 이벤트와는 다른 점- hover는 실제 마우스 이벤트에 의해서만 발생
(터치 입력에서 브라우저가 흉내 낸 마우스 이벤트는 무시)

<motion.a
  whileHover={{ scale: 1.2 }}
  onHoverStart={event => {
    // 마우스가 올라갔을 때 실행
  }}
  onHoverEnd={event => {
    // 마우스가 벗어났을 때 실행
  }}
/>

Tab

같은 컴포넌트에서 눌렀다가 뗐을 때를 감지

✅ 드래그와 함께 쓸 때 주의사항
만약 tap 가능한 컴포넌트가 드래그 가능한 컴포넌트 안에 포함되어 있다면
제스처 중에 포인터가 3픽셀 이상 움직이면 tap이 자동 취소
즉, 클릭이 아닌 드래그로 판단되어 tap 이벤트가 실행 X

⌨️ 키보드 동작 흐름
Enter 키를 누를 때
onTapStart 실행됨
whileTap 애니메이션 시작됨

Enter 키를 뗄 때
onTap 실행됨

Enter를 누르고 있는 동안 포커스를 잃으면
onTapCancel 실행됨

Pan

사용자가 요소를 누른 채로 3픽셀 이상 이동할 때 인식

<motion.div onPan={(e, pointInfo) => {}} /> 
//onPan은 포인터가 움직이는 동안 계속 호출
//pointInfo에는 현재 위치, 속도 등 유용한 정보 담김

⚠️ 주의 사항
pan에는 아직 whilePan 같은 애니메이션용 prop은 없음
→ 대신 onPanStart, onPan, onPanEnd 같은 이벤트 핸들러를 사용

📱 터치 입력에서 주의할 점
모바일 등 터치 디바이스에서 pan 제스처가 제대로 작동하려면
기본 터치 스크롤을 비활성화해야함

.touch-area {
  touch-action: none;       /* x, y 모두 차단 */
  /* 또는 touch-action: pan-y; (세로 스크롤만 허용) */
}

이 설정을 안 하면 브라우저가 터치 이벤트를 스크롤로 처리해서 pan 이벤트가 막힐 수 있음

Drag

사용자의 포인터 움직임을 컴포넌트의 x축과 y축에 적용

drag 만 적으면 기본적으로 x, y 방향으로 드래그 가능
whileDrag은 드래그 중일 때 적용되는 애니메이션

<motion.div
  drag
  whileDrag={{ scale: 1.2, backgroundColor: "#f00" }}
/>

🌀 기본 동작: 관성 애니메이션
드래그가 끝나면 요소는 마지막 속도를 따라 관성 애니메이션을 자동으로 실행 (약간 튕기듯 멈추는 자연스러운 느낌)
끄고 싶으면 <motion.div drag dragMomentum={false} /> 설정

dragTransition prop을 사용해서 관성의 속도, 제동, 제한 값 등을 커스터마이징

<motion.div
  drag
  dragTransition={{ bounceStiffness: 300, timeConstant: 200 }}
/>
옵션역할값이 클수록
bounceStiffness튕기는 힘 (경계에 닿았을 때)더 강하게, 빠르게 튕김
timeConstant관성 애니메이션의 감속 속도더 오래 미끄러짐

Constraints (드래그 제약)

  • 객체 형태로 직접 지정
// x축만 드래그할 수 있고,
// 왼쪽으로는 0px, 오른쪽으로는 300px까지만 이동 가능
<motion.div
  drag="x"
  dragConstraints={{ left: 0, right: 300 }}
/>
  • Ref로 다른 요소를 제약 영역으로 지정
const MyComponent = () => {
  const constraintsRef = useRef(null)

  return (
     <motion.div ref={constraintsRef}>
         <motion.div drag dragConstraints={constraintsRef} />
     </motion.div>
  )
}

dragElastic 드래그 바깥으로의 "튕김" 정도 조절
기본적으로 드래그 요소가 제약 범위(dragConstraints)를 넘어가면
약간 늘어나듯 끌려가는 탄성 효과(elasticity)가 적용
이 효과는 dragElastic 값을 통해 조절

<motion.div
  drag
  dragConstraints={{ left: 0, right: 300 }}
  dragElastic={0.5}
/>

Direction locking (드래그 방향 고정)

dragDirectionLock을 설정하면 드래그 시작 시 처음 움직인 방향(가로 or 세로)으로만 드래그가 가능하게 만들 수 있음

<motion.div
  drag
  dragDirectionLock
  onDirectionLock={(axis) => {
    console.log(`Locked to ${axis} axis`) 
  }}
/>

사용자가 드래그를 시작하면 포인터가 먼저 움직인 방향(x 또는 y)을 감지해서
해당 방향으로만 드래그할 수 있도록 고정(lock)

예를 들어:
세로로 먼저 움직였으면 → 세로(y축)로만 이동 가능
가로로 먼저 움직였으면 → 가로(x축)로만 이동 가능

드래그 방향이 감지되면 onDirectionLock 콜백이 실행되고
파라미터로 "x" 또는 "y" 중 하나가 전달

Focus

컴포넌트가 포커스를 얻거나 잃을 때를 감지
동작 기준은 CSS의 :focus-visible 선택자와 동일

같은 요소가 마우스 클릭 또는 키보드(Tab 등)로 포커스를 얻을 때
버튼, 링크 등 다른 요소가 키보드 접근성 방식(예: Tab 키)으로 포커스될 때

a 태그에 포커스가 되면 살짝 확대
보통 키보드 Tab으로 포커스가 이동될 때 작동

<motion.a whileFocus={{ scale: 1.2 }} href="#" />

이벤트 전파 제어

자식 요소에서 onPointerDownCapture 같은 React의 Capture 이벤트 핸들러를 사용하면 부모 motion 컴포넌트로의 포인터 이벤트 전파를 막을 수 있음

<motion.div whileTap={{ scale: 2 }}>
  <button onPointerDownCapture={e => e.stopPropagation()} />
</motion.div>

위 코드에서 부모 <motion.div>whileTap으로 탭 애니메이션을 가지지만 m자식 button을 클릭할 경우 e.stopPropagation() 때문에 부모의 whileTap 애니메이션이 발동 X

🧠 왜 써야 할까?
드래그 가능한 카드 안에 클릭 가능한 버튼이 있을 때 부모에 적용된 탭/드래그 제스처가 자식 요소에서 방해되지 않게 하려면 자식에서 e.stopPropagation()을 써서 이벤트 전파를 차단해야 함

이벤트 핸들러설명
onPointerDownCapture포인터 누름 이벤트를 캡처 단계에서 감지
onClickCapture클릭 이벤트 감지

SVG 필터에서는 제스처가 인식 ❌

제스처를 직접 걸 수는 없지만 부모 요소에 이벤트 핸들러나 while- 속성을 적용하고 필터 요소는 variants로 애니메이션 처리

const MyComponent = () => {
  return (
    <motion.svg whileHover="hover">
      <filter id="blur">
        <motion.feGaussianBlur
          stdDeviation={0} // 기본 블러 강도는 0
          variants={{ hover: { stdDeviation: 2 } }} //  blur 강도를 2로 증가시킴
        />
      </filter>
    </motion.svg>
  )
}

Variants (변수)

DOM 전체에 걸쳐 애니메이션을 조정하고 싶을 때

const variants = {
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
}
<motion.div variants={variants} />

라벨 참조 가능

<motion.div
  variants={variants}
  initial="hidden"
  whileInView="visible"
/>

initial="hidden" 초기 상태에서는 요소가 보이지 않음
whileInView="visible" 요소가 화면에 보이면 점차적으로 나타남

상태를 animate에 전달하기만 하면, 이제 모든 애니메이션 타겟을 정의할 수 있는 깔끔한 장소 생김

const [status, setStatus] = useState<"inactive" | "active" | "complete">(
  "inactive"
);

<motion.div
  animate={status} 
  variants={{
    inactive: { scale: 0.9 color: "var(--gray-500)" },
    active: { scale: 1 color: "var(--blue-500)" },
    complete: { scale: 1 color: "var(--blue-500)" }
  }}

Propagation(전파)

이것은 애니메이션 타겟을 재사용하고 결합하는 데 유용하지만 애니메이션을 트리 구조 전체에서 조정할 때 더욱 강력
ul이 뷰포트에 들어오면, "visible" 변형을 가진 모든 자식 요소들도 함께 애니메이션이 적용

const list = {
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
}

const item = {
  visible: { opacity: 1, x: 0 },
  hidden: { opacity: 0, x: -100 },
}

return (
  <motion.ul
    initial="hidden"
    whileInView="visible"
    variants={list}
  >
    <motion.li variants={item} />
    <motion.li variants={item} />
    <motion.li variants={item} />
  </motion.ul>
)

Orchestration (조정)

기본적으로 자식 요소들의 애니메이션은 부모와 동시에 시작
하지만 Variants를 사용하면 새로운 transition 가능

beforeChildren 부모 애니메이션이 먼저 실행되고 나서 자식들이 실행
afterChildren 자식들이 먼저 실행되고 나서 부모가 실행
staggerDirection 자식 애니메이션 순서의 방향 설정
1: 순방향 (첫 번째 → 마지막) / -1: 역방향 (마지막 → 첫 번째)
delayChildren 지연

const list = {
  visible: {
    opacity: 1,
    transition: {
      when: "beforeChildren",
      staggerChildren: 0.3, // 자식 요소들을 0.3초 간격으로 순차적으로 실행
    },
  },
  hidden: {
    opacity: 0,
    transition: {
      when: "afterChildren",
    },
  },
}

동적 variants (Dynamic variants)

각 variant는 활성화될 때 실행되는 함수 형태로 정의
variants를 함수로 만들고 custom
d값을 넘겨주면, 요소마다 딜레이나 속도 등을 개별적으로 제어

const variants = {
  hidden: { opacity: 0 },
  visible: (index) => ({
    opacity: 1,
    transition: { delay: index * 0.3 }
  })
}

items.map((item, index) => (
  <motion.div custom={index} variants={variants} />
))

Animation Controls

애니메이션 재생을 수동으로 제어해야 하는 경우

useAnimate 훅은 다음과 같은 상황에서 유용

  • motion 컴포넌트가 아닌 일반 HTML 또는 SVG 요소에 애니메이션을 적용할 때
  • 복잡한 애니메이션 시퀀스가 필요할 때
  • 애니메이션을 시간, 속도, play(), pause() 등의 재생 컨트롤로 직접 제어할 때
function MyComponent() {
  const [scope, animate] = useAnimate()

  useEffect(() => {
    const controls = animate([
      [scope.current, { x: "100%" }], //  전체 <ul>에 x축으로 100% 이동
      ["li", { opacity: 1 }] // <li> 요소들의 opacity를 1로 설정
    ])

    controls.speed = 0.8 //  전체 애니메이션 속도를 80%로 조절

    return () => controls.stop() // 컴포넌트가 언마운트될 때 애니메이션 중지
  }, [])

  return (
    <ul ref={scope}>
      <li />
      <li />
      <li />
    </ul>
  )
}

layout

layout 속성 하나만 있으면 복잡한 레이아웃 변경도 부드럽게 애니메이션
심지어 일반 CSS 애니메이션으로는 불가능한 속성들도 애니메이션이 가능

<motion.div layout />

flex-direction,justify-content, grid-template-columns
이런 속성들은 원래 CSS transition으로는 부드럽게 바꾸기 힘든데 layout 애니메이션을 쓰면 자연스럽게 가능

<motion.div
  layout
  style={{ justifyContent: isOn ? "flex-start" : "flex-end" }}
/>

layoutId="underline" Shared layout animation의 핵심
서로 다른 컴포넌트라도 layoutId가 같으면 동일한 컴포넌트처럼 애니메이션을 이어서 보여주는 기능

 <ul>
      {tabs.map((tab) => (
        <motion.li
          key={tab.index}
          onClick={() => setSelectedTab(tab)}
          style={tabStyle} // 클릭 시 이동하는 탭 
        >
          {selectedTab.label === tab.label && (
            <motion.div
              layoutId="underline"
              style={underline}
            />
          )}
        </motion.li>
      ))}
    </ul>

이 코드에선 탭을 클릭할 때마다 밑줄(underline)이 움직이는데 실제로는 컴포넌트가 새로 생겼다가 사라지는 거야.

근데 layoutId가 "underline"으로 동일하기 때문에 Framer Motion이 이걸 "같은 요소"로 간주하고 부드럽게 위치를 옮겨주는 애니메이션을 실행

AnimatePresence

퇴장(exit) 애니메이션을 쉽게 구현할 수 있도록 도와주는 컴포넌트
컴포넌트가 DOM에 계속 존재하도록 하여 퇴장 애니메이션이 끝날 때까지 해당 컴포넌트를 화면에 유지하도록 함

import { AnimatePresence } from "motion/react"
<AnimatePresence>
  {isOpen && <motion.div layoutId="modal" />}
</AnimatePresence>

모달 예시

<>
  <motion.button
    layoutId="modal"
    onClick={() => setIsOpen(true)}
    transition={{ type: "spring" }}
  >
    Open
  </motion.button>
  <AnimatePresence>
    {isOn && (
      <motion.dialog
        layoutId="modal"
        transition={{ duration: 0.3 }}
      />
    )}
  </AnimatePresence>
</>

layoutScroll

motion.div에 layoutScroll prop을 추가하면, Motion은 자식 요소의 레이아웃을 측정할 때 해당 컨테이너의 스크롤 오프셋을 추적

이로 인해 스크롤된 상태에서도 애니메이션이 올바르게 조정

<motion.div layoutScroll style={{ overflow: "scroll" }} />

고정된 컨테이너 내에서 애니메이션

고정된 컨테이너(예: position: "fixed"인 컨테이너) 내에서 요소를 애니메이션화할 때 페이지의 스크롤 오프셋을 고려하도록 Motion에게 알려줘야 함

layoutRoot 페이지의 스크롤 오프셋을 고려하도록 하여 고정된 컨테이너 내에서 애니메이션화되는 요소들이 정확하게 애니메이션 되도록 보장함
고정 위치의 요소는 페이지 전체의 스크롤 영향을 받을 수 있기 때문에 이 속성이 중요

<motion.div layoutRoot style={{ position: "fixed" }} />

그룹 레이아웃 애니메이션

하나의 컴포넌트가 리렌더링될 때 성능상의 이유로 다른 컴포넌트는 그 변경 사항을 감지하지 못할 수 있는데 이런 문제를 해결하려면 여러 컴포넌트 간 레이아웃 변경을 동기화 필요

LayoutGroup 여러 컴포넌트의 레이아웃을 동기화하여 하나의 컴포넌트가 리렌더링 될 때 다른 컴포넌트들도 함께 레이아웃 애니메이션을 동기화할 수 있게 해줌

import { LayoutGroup } from "motion/react"

function List() {
  return (
    <LayoutGroup>
      <Accordion />
      <Accordion />
    </LayoutGroup>  
  )
}

스케일 보정 (Scale correction)

transform을 사용한 레이아웃 애니메이션은 때때로 내부 자식 요소들을 시각적으로 왜곡시킬 수 있음 이러한 왜곡을 보정하려면 해당 요소의 첫 번째 자식 요소에도 layout 속성을 부여

트러블슈팅 (문제 해결)

컴포넌트가 애니메이션되지 않아요
display: inline이 설정되어 있지 않은지 확인하세요.
👉 브라우저는 inline 요소에 transform을 적용하지 않아요.

레이아웃 애니메이션이 시작되길 기대하는 시점에 컴포넌트가 리렌더링 되고 있는지도 확인하세요.

SVG 레이아웃 애니메이션이 깨져요
현재 SVG 컴포넌트는 layout 애니메이션을 지원하지 않아요.

👉SVG는 레이아웃 시스템이 없기 때문에 cx, r 등의 속성 자체를 직접 애니메이션 하는 것을

콘텐츠가 이상하게 늘어나요
width나 height를 scale로 애니메이션할 때 생기는 자연스러운 부작용이에요.

👉 해당 요소에 layout 애니메이션을 지정하면 scale 보정이 되어 자연스러워집니다.

<motion.section layout>
  <motion.img layout />
</motion.section>

만약 이미지나 텍스트가 다른 비율로 바뀌는 상황이라면 layout="position"으로 설정하는 게 더 좋아요.

border-radius나 box-shadow가 이상해요
scale 애니메이션은 성능이 좋지만 border-radius, box-shadow 같은 스타일은 왜곡될 수 있어요.

Motion은 이 스타일들의 왜곡을 자동으로 보정해주지만, 스타일이 인라인으로 직접 설정되어 있어야 해요.

<motion.div layout style={{ borderRadius: 20 }} />

애니메이션 중 Border가 왜곡되어 보이는 이유

  1. border는 layout 재계산을 유발해요
    border 값이 바뀌면 레이아웃 전체를 다시 계산해야 하므로 transform을 사용하는 Motion의 성능 이점을 없애버려요.
    이런 경우엔 오히려 전통적인 방식으로 width / height를 직접 애니메이션하는 게 나을 수 있어요.

  2. 브라우저에서 border는 최소 1px 이하로 렌더링할 수 없어요
    그래서 scale을 이용한 애니메이션 중에는 Motion이 border에 대해 정확하게 보정(scale correction) 해줄 수 없어요.

👉 border를 padding으로 대체해요

<motion.div layout style={{ borderRadius: 10, padding: 5 }}>
  <motion.div layout style={{ borderRadius: 5 }} />
</motion.div>

바깥 요소가 padding으로 여백을 주고 내부 요소가 실제 콘텐츠 역할을 하게 해서 border처럼 보이게 만들 수 있어요.

0개의 댓글