Motion — svelte-motion으로 고급 애니메이션 구현하기
transition은 요소의 등장/퇴장에 쓰고, motion은 값의 부드러운 변화에 씁니다.
개념 정의
Motion 은 숫자 값을 부드럽게 보간(interpolate)하는 시스템입니다. Svelte는 tweened(이징 기반)와 spring(물리 기반) 두 가지 motion primitive를 내장합니다.
tweened — 이징 기반 보간
<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 — 물리 기반 보간
<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>
카운터 애니메이션
<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>
드래그 앤 드롭
<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 값 자체를 보간하므로, 숫자 표시나 위치 추적 같은 동적 애니메이션에 강합니다.
댓글 로딩 중...