[FM] Animation - Gestures

0

Framer-motion

목록 보기
5/5
post-thumbnail

Gesture 기능은 Framer motion에서 제공하는 아주 강력하고 편리한 기능으로, 브라우저에서 사용자의 제스처 모션을 인식할 수 있게 해줍니다. Gesture 기능에 대해서 심층적으로 알아보겠습니다.

Animation helpers

motion 컴포넌트에 drag, tap, hover, pan등의 제스처 이벤트 리스너를 달아줄 수 있습니다.

motion 컴포넌트는 whileHover, whileTap, whileFocus등과 같은 다수의 애니메이션 prop을 제공합니다. 해당 제스쳐가 발생할 때 임시로 재생할 애니메이션 객체를 값으로 넣어주면 됩니다. Framer motion에서는 이러한 while+제스쳐명으로 이루어진 prop들을 helper라고 부릅니다.

<motion.button
  whileHover={{
    scale: 1.2,
    transition: { duration: 1 },
  }}
  whileTap={{ scale: 0.9 }}
/>

다음과 같이 variant를 사용할수도 있습니다.

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

동시에 여러 제스쳐가 하나의 컴포넌트에 걸리는 경우도 있습니다. 예를 들어 호버상태와 탭 상태가 겹치는 경우도 있을 수 있겠죠? 그런데 이런 경우는 어떤 제스쳐가 먼저 시작했으며 언제 어떤 제스쳐가 끝났는지에 따라 Framer motion이 우선순위를 판별하여 자연스럽게 재생할 애니메이션을 선택합니다.

Propagation

propagation을 방지하려면 다음과 같이 리액트에서 제공하는 Capture prop으로 e.stopPropagation()함수를 실행시켜주면 됩니다.

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

SVG 필터에 대한 참고사항

SVG filter 컴포넌트들은 특이하게도 물리적 실체가 없는것으로 인식되어 다이렉트로 while helper를 달아줄 수 없습니다. 따라서 다음과 같이 리액트 상태를 이용하여 유사한 효과를 내줘야 합니다.

const MyComponent = () => {
  const [isHovered, setHovered] = useState(false)

  // Simplified example
  return (
    <svg>
      <image
        filter="url(#blur)"
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
      />
      <filter id="blur">
        <motion.div
          initial={false}
          animate={{ stdDeviation: isHovered ? 0 : 2 }}
        />
      </filter>
    </svg>
  )
}

Hover

Hover 제스쳐는 포인터가 컴포넌트 내에 위치할 때와 밖으로 나갈 때를 감지합니다.

호버 상태는 onMouseEnteronMouseLeave와는 다릅니다. 호버 상태는 실제 마우스 포인터를 기준으로 동작합니다. 터치로는 호버상태를 만들 수 없습니다.

<motion.a
  whileHover={{ scale: 1.2 }}
  onHoverStart={e => {}}
  onHoverEnd={e => {}}
/>

다음은motion 컴포넌트에 줄 수 있는 호버 상태 관련 prop들입니다.

whileHover

  • 의미: 호버상태가 유지되는 동안 재생할 애니메이션
  • 값: variant 라벨 또는 애니메이션 객체
<motion.div whileHover={{ scale: 1.2 }} />

onHoverStart

  • 의미: 호버 상태에 진입할 때 호출할 콜백 함수
  • 값: (event: MouseEvent, info:EventInfo) => void 형식의 함수
<motion.div onHoverStart={() => console.log('Hover starts')} />

onHoverEnd

  • 의미: 호버 상태가 멈출 때 호출할 콜백 함수
  • 값: (event: MouseEvent, info:EventInfo) => void 형식의 함수
<motion.div onHoverEnd={() => console.log("Hover ends")} />

Focus

Focus 제스쳐는 CSS규칙에 따라 focus상태가 정의된 엘리먼트에 포커스를 얻거나 잃은 상태를 감지합니다.

전형적으로 input 엘리먼트가 이 focus상태를 얻기도, 잃기도 할 수 있는 엘리먼트입니다. 포커스는 마우스나 키보드 등으로 줄 수 있습니다.

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

다음은motion 컴포넌트에 줄 수 있는 포커스 상태 관련 prop들입니다.

whileFocus

  • 의미: 포커스 상태가 유지되는 동안 재생할 애니메이션
  • 값: variant 라벨 또는 애니메이션 객체
<motion.div whileFocus={{ scale: 1.2 }} />

Tap

Tap 제스쳐는 주 포인터(마우스 좌클릭 또는 첫 터치포인트 등)가 동일한 컴포넌트에 위에서 눌러졌다가 떼어질 때를 감지합니다.

탭 제스쳐는 항상 포인터가 프레스다운된 타겟부터 릴리즈되는 타겟이 동일해야 인식됩니다. 예를 들어, 버튼에 포인터를 올려놓고 좌클릭한 뒤, 클릭한 상태를 유지하다가 버튼 위에서 릴리즈해야 인식됩니다. 버튼 밖에서 릴리즈하면 그것은 탭이 아닌 tapCancel이벤트라고 합니다.

접근성

탭 이벤트의 발생 대상이 되는 엘리먼트들은 모두 키보드로 접근 가능합니다.

tap prop을 가진 모든 엘리먼트는 포커스를 받을 수 있고, 포커스된 상태에서 Enter키를 사용하여 탭 이벤트를 트리거할 수 있습니다.

  • 엔터키 프레스다운시 onTapStartwhileTap을 트리거
  • 엔터키 릴리즈시 onTap 트리거
  • 엔터키가 릴리즈되기 전에 포커스를 잃을시 onTapCancel 트리거

다음은motion 컴포넌트에 줄 수 있는 탭 상태 관련 prop들입니다.

whileTap

  • 의미: 컴포넌트를 포인터로 누르고 있는 동안 재생될 애니메이션
  • 값: variant 라벨 또는 애니메이션 객체
<motion.div whileTap={{ scale: 1.2 }} />

onTap

  • 의미: 탭 제스쳐가 성공적으로 종료되었을 때 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:TapInfo) => void 형식의 함수
function onTap(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onTap={onTap} />

onTapStart

  • 의미: 탭 제스쳐가 시작될 때 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:TapInfo) => void 형식의 함수
function onTapStart(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onTapStart={onTapStart} />

onTapCancel

  • 의미: 탭 제스쳐가 엘리먼트 밖에서 종료되었을 때 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:TapInfo) => void 형식의 함수
function onTapCancel(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onTapCancel={onTapCancel} />

Pan

Pan 제스쳐는 어떤 컴포넌트에 포인터가 프레스된 상태로 3픽셀 이상 움직이는 것을 감지합니다.

<motion.div onPan={(e, pointInfo) => {}} />

다음은motion 컴포넌트에 줄 수 있는 팬 상태 관련 prop들입니다.

onPan

  • 의미: 팬 제스쳐가 엘리먼트에서 감지되었을 때 호출할 콜백함수

    🛑주의사항: 팬 제스쳐가 터치인풋으로 정상 작동하려면, 해당 엘리먼트가 touch scolling이 disabled되어 있어야 합니다. CSS 규칙인 touch-action 속성을 수정하여 disable시킬 수 있습니다.

  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수

    💡PanInfo 객체란?
    다음 값들에 대한 x, y값을 가진 객체를 의미합니다.

    • point: 디바이스나 페이지에 대한 좌표값
    • delta: 마지막 이벤트로부터 멀어진 거리(움직인 거리)
    • offset: 기존 팬 이벤트로부터의 오프셋
    • velocity: 포인터의 현재 속도
function onPan(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onPan={onPan} />

onPanStart

  • 의미: 팬 제스쳐가 시작될 때 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수
function onPanStart(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onPanStart={onPanStart} />

onPanEnd

  • 의미: 팬 제스쳐가 종료될 때 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수
function onPanEnd(event, info) {
  console.log(info.point.x, info.point.y)
}

<motion.div onPanEnd={onPanEnd} />

Drag

Drag 제스쳐는 기본적으로 Pan 제스쳐와 동일한 룰을 따르지만, 제스쳐가 적용된 컴포넌트의 x, y축을 기준으로 포인터의 움직임이 적용됩니다. 이해를 돕기 위한 예시로 피그마를 들어보겠습니다. 피그마에서 H(hand) 툴로 문서 전체에 대한 시야를 바꿀 때는 팬 제스쳐이고, V툴로 어떤 요소를 잡고 이동시킬 때는 드래그 제스쳐 입니다.

<motion.div drag />

다음은motion 컴포넌트에 줄 수 있는 드래그 상태 관련 prop들입니다.

whileDrag

  • 의미: 드래그 제스쳐가 감지되었을 때 재생할 애니메이션
  • 값: variant 라벨 또는 애니메이션 객체
<motion.div whileDrag={{ scale: 1.2 }} />

drag

  • 의미: 해당 엘리먼트에 대해서 드래그를 가능하게 하겠다는 의미, 디폴트로 fault가 주어진다. "x" 또는 "y"값을 주어 특정 방향만 드래그 가능하게 줄 수도 있다.
  • 값: 불린값 또는 "x" 또는 "y"
<motion.div drag="x" />

dragContraints

  • 의미: 드래그가 허용된 범위에 대한 정보이며, top, left, right, bottom 픽셀 단위 값을 가진 객체로 줄 수 있다. 이 객체가 드래그 가능한 컴포넌트의 범위를 정의한다. 또한 리액트의 useRef훅을 통해 다른 컴포넌트의 ref값을 가질 수도 있다. 그러면 해당 ref객체를 constraints로 사용하겠다는 뜻이 된다.
  • 값: false | Partial<BoundingBox2D> | RefObject<Element>
// In pixels
<motion.div
  drag="x"
  dragConstraints={{ left: 0, right: 300 }}
/>

// As a ref to another component
const MyComponent = () => {
  const constraintsRef = useRef(null)

  return (
     <motion.div ref={constraintsRef}>
         <motion.div drag dragConstraints={constraintsRef} />
     </motion.div>
  )
}
  • 드래그 가능한 자식 div에 부모 divrefdragConstraints로 넘겨줌으로써 부모 div안에서 드래그 가능하게 한 예제 코드

dragSnapToOrigin

  • 의미: 해당 엘리먼트가 드래그 제스쳐가 종료되면 드래그 되기 전의 원래 포지션으로 돌아갈 지 여부

dragElastic

  • 의미: contraint 바깥으로 허용된 움직임 정도, 디폴트로 0.5가 주어진다.
  • 값: 0 ~ 1사이의 number
<motion.div
  drag
  dragConstraints={{ left: 0, right: 300 }}
  dragElastic={0.2}
/>

dragMomentum

  • 의미: 드래그가 종료될 때 모멘텀의 유무, 디폴트로 true가 주어진다.
  • 값: 불린값
<motion.div
  drag
  dragConstraints={{ left: 0, right: 300 }}
  dragMomentum={false}
/>

dragTransition

  • 의미: 드래그 가능한 프레임을 릴리즈할 때 inertia 타입의 애니메이션이 자동으로 재생되는데, dragTransition prop으로 이 애니메이션을 커스텀할 수 있다.
  • 값: InertiaOptions
<motion.div
  drag
  dragTransition={{ bounceStiffness: 600, bounceDamping: 10 }}
/>

dragPropagation

  • 의미: 드래그 제스쳐의 자식으로의 전파를 허용할 지 여부. 디폴트로 false가 주어진다.
  • 값: 불린값
<motion.div drag="x" dragPropagation />

dragControls

  • 의미: 드래그 제스쳐를 다른 컴포넌트상의 포지션에서 시작하고 싶은 경우 제공할 수 있는 객체. 해당 객체는 useDragControls 훅으로 생성할 수 있고, 생성된 객체의 start메서드로 드래그 제스쳐를 이니시에이트 할 수 있다.
  • 값: DragControls
const dragControls = useDragControls()

function startDrag(event) {
  dragControls.start(event, { snapToCursor: true })
}

return (
  <>
    <div onPointerDown={startDrag} />
    <motion.div drag="x" dragControls={dragControls} />
  </>
)

참고: dragControls를 쓰면 드래그 제스쳐의 이니시에이팅을 개발자가 컨트롤할 수 있다는 뜻이며, dragListener={false}를 줌으로써 drag prop을 준 엘리먼트가 드래그 제스쳐 이니시에이션을 할 수 없도록 막을 수도 있습니다.

dragListener

  • 의미: 이벤트 리스너로부터 드래기 제스쳐를 트리거 할 지 여부이며, 이 값이 false인 경우 일반적으로 포인터를 사용해서 드래그를 시도해도 드래그 제스쳐가 이니시에이팅 되지 않는다. 오직 controls를 통해 이니시에이트 된다.
  • 값: 불린값

onDrag

  • 의미: 컴포넌트가 드래그되면 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수
<motion.div
  drag
  onDrag={
    (event, info) => console.log(info.point.x, info.point.y)
  }
/>

onDragStart

  • 의미: 드래그 제스쳐가 시작되면 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수
<motion.div
  drag
  onDragStart={
    (event, info) => console.log(info.point.x, info.point.y)
  }
/>

onDragEnd

  • 의미: 드래그 제스쳐가 종료되면 호출할 콜백함수
  • 값: (event: MouseEvent | TouchEvent | PointerEvent, info:PanInfo) => void 형식의 함수
<motion.div
  drag
  onDragEnd={
    (event, info) => console.log(info.point.x, info.point.y)
  }
/>

onDirectionLock

  • 의미: 드래그 방향이 결정되면 호출될 콜백함수
  • 값: (axis: "x" | "y" => void 형식의 함수
<motion.div
  drag
  dragDirectionLock
  onDirectionLock={axis => console.log(axis)}
/>

0개의 댓글