transition은 요소의 등장/퇴장에 쓰고, motion은 값의 부드러운 변화에 씁니다.

개념 정의

Motion 은 숫자 값을 부드럽게 보간(interpolate)하는 시스템입니다. Svelte는 tweened(이징 기반)와 spring(물리 기반) 두 가지 motion primitive를 내장합니다.

tweened — 이징 기반 보간

SVELTE
<script>
  import { tweened } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  const progress = tweened(0, { duration: 400, easing: cubicOut });
</script>

<button onclick={() => progress.set(0)}>0%</button>
<button onclick={() => progress.set(0.5)}>50%</button>
<button onclick={() => progress.set(1)}>100%</button>

<div class="progress-bar">
  <div class="fill" style:width="{$progress * 100}%"></div>
</div>

<p>{Math.round($progress * 100)}%</p>

<style>
  .progress-bar { width: 100%; height: 20px; background: #eee; border-radius: 10px; overflow: hidden; }
  .fill { height: 100%; background: #ff3e00; transition: none; }
</style>

spring — 물리 기반 보간

SVELTE
<script>
  import { spring } from 'svelte/motion';

  // stiffness: 탄성 (높을수록 빠르게), damping: 감쇠 (낮을수록 많이 흔들림)
  const coords = spring({ x: 0, y: 0 }, {
    stiffness: 0.1,
    damping: 0.25,
  });

  const size = spring(1, { stiffness: 0.3, damping: 0.5 });
</script>

<svelte:window onmousemove={(e) => coords.set({ x: e.clientX, y: e.clientY })} />

<div
  class="follower"
  style:left="{$coords.x}px"
  style:top="{$coords.y}px"
  style:transform="scale({$size})"
  onmouseenter={() => size.set(2)}
  onmouseleave={() => size.set(1)}
></div>

<style>
  .follower {
    position: fixed;
    width: 30px;
    height: 30px;
    background: #ff3e00;
    border-radius: 50%;
    pointer-events: auto;
    transform-origin: center;
    translate: -50% -50%;
  }
</style>

카운터 애니메이션

SVELTE
<script>
  import { tweened } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  const count = tweened(0, { duration: 1000, easing: cubicOut });

  function animateTo(target) {
    count.set(target);
  }
</script>

<div class="counter">{Math.floor($count).toLocaleString()}</div>

<button onclick={() => animateTo(1000)}>1,000</button>
<button onclick={() => animateTo(50000)}>50,000</button>
<button onclick={() => animateTo(1000000)}>1,000,000</button>

드래그 앤 드롭

SVELTE
<script>
  import { spring } from 'svelte/motion';

  const position = spring({ x: 0, y: 0 }, { stiffness: 0.2, damping: 0.4 });
  let isDragging = $state(false);
  let offset = { x: 0, y: 0 };

  function startDrag(e) {
    isDragging = true;
    const rect = e.currentTarget.getBoundingClientRect();
    offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
  }

  function drag(e) {
    if (!isDragging) return;
    position.set({ x: e.clientX - offset.x, y: e.clientY - offset.y });
  }

  function endDrag() {
    isDragging = false;
  }
</script>

<svelte:window onmousemove={drag} onmouseup={endDrag} />

<div
  class="draggable"
  class:dragging={isDragging}
  style:left="{$position.x}px"
  style:top="{$position.y}px"
  onmousedown={startDrag}
>
  드래그하세요
</div>

<style>
  .draggable {
    position: absolute;
    padding: 1rem 2rem;
    background: #ff3e00;
    color: white;
    border-radius: 8px;
    cursor: grab;
    user-select: none;
  }
  .dragging { cursor: grabbing; }
</style>

면접 포인트

  • "tweened와 spring의 차이는?": tweened는 정해진 duration과 easing 함수로 예측 가능한 애니메이션을 제공합니다. spring은 물리 시뮬레이션으로 자연스러운 관성과 탄성 효과를 줍니다.
  • "CSS 애니메이션 대신 motion을 쓰는 이유는?": CSS로는 JavaScript 값의 보간이 어렵습니다. 숫자 카운트업, 마우스 따라다니기, 드래그 같은 동적 값 기반 애니메이션에 motion이 적합합니다.

정리

Motion은 "값을 부드럽게 변화시키는" 도구입니다. tweened는 예측 가능한 이징, spring은 자연스러운 물리 효과를 제공합니다. CSS transition과 다르게 JavaScript 값 자체를 보간하므로, 숫자 표시나 위치 추적 같은 동적 애니메이션에 강합니다.

댓글 로딩 중...