Motion (spring / tweened)
Svelte’s svelte/motion module ships tweened and spring — reactive stores that smoothly interpolate values over time. Pair them with $ auto-subscriptions and your component animates as soon as you set a target, with no animation loop boilerplate.
tweened, spring, interpolate, examples
EXAMPLE
<script>
import { tweened, spring } from 'svelte/motion';
import { cubicOut, elasticOut, quintOut } from 'svelte/easing';
// 1) tweened — interpolate over a fixed DURATION
const progress = tweened(0, {
duration: 400,
easing: cubicOut,
});
function newTarget() {
progress.set(Math.random());
}
</script>
<button on:click={newTarget}>Random target</button>
<progress value={$progress}></progress>
<p>{($progress * 100).toFixed(0)}%</p>
<!-- 2) spring — physics-based; settles via stiffness + damping -->
<script>
const pos = spring(0, { stiffness: 0.1, damping: 0.4 });
function nudge() { pos.update((p) => p + 100); }
</script>
<div style="transform: translateX({$pos}px)">Springs</div>
<button on:click={nudge}>Nudge</button>
<!-- spring overshoots + settles. Great for natural-feeling UI gestures. -->
<!-- 3) Animating an OBJECT — tween/spring multiple values together -->
<script>
const point = spring({ x: 0, y: 0 }, { stiffness: 0.15, damping: 0.6 });
function handleMouse(e) {
point.set({ x: e.clientX, y: e.clientY });
}
</script>
<svelte:window on:mousemove={handleMouse} />
<div class="dot" style="transform: translate({$point.x}px, {$point.y}px)">●</div>
<!-- 4) Custom interpolation — interpolate non-number types -->
<script>
import { interpolate } from 'd3-interpolate';
const color = tweened('#4f46e5', {
duration: 400,
interpolate: (a, b) => interpolate(a, b), // smooth color transitions
});
</script>
<div style="background: {$color}; padding: 24px">
Color
</div>
<button on:click={() => color.set('#ef4444')}>Red</button>
<button on:click={() => color.set('#22c55e')}>Green</button>
<!-- 5) Update vs set
progress.set(0.5); // jump straight to new target
progress.update((p) => p + 0.1); // increment
progress.set(0.5, { duration: 1000 }); // override duration this time -->
<!-- 6) Spring options
// stiffness 0..1 — higher = snappier
// damping 0..1 — higher = less bouncing
// precision small — when to stop (close enough)
const snappy = spring(0, { stiffness: 0.3, damping: 0.9 });
const loose = spring(0, { stiffness: 0.05, damping: 0.2 });
-->
<!-- 7) Combine with CSS transforms for GPU-friendly animation
<div class="card" style="transform: scale({$scale}) rotate({$rotation}deg)">…</div>
.card { will-change: transform; transition: none; }
-->
<!-- 8) Animating with a 'progress' between two values -->
<script>
const t = tweened(0, { duration: 500 });
function animateTo(value) { t.set(value); }
$: r = 50 + $t * 100;
$: cx = 100 + $t * 200;
</script>
<svg viewBox="0 0 600 200">
<circle {cx} cy="100" {r} fill="steelblue" />
</svg>
<button on:click={() => t.set($t === 1 ? 0 : 1)}>Toggle</button>
<!-- 9) tweened number formatting helper -->
<script>
const counter = tweened(0, { duration: 1000, easing: quintOut });
function increase() { counter.update((n) => n + 1000); }
</script>
<button on:click={increase}>+1000</button>
<h2>{Math.round($counter).toLocaleString()}</h2>
<!-- 10) Drag-and-spring — natural release -->
<script>
const pos2 = spring({ x: 0, y: 0 }, { stiffness: 0.4, damping: 0.7 });
let dragging = false;
let offset = { x: 0, y: 0 };
function down(e) { dragging = true; offset = { x: e.clientX - $pos2.x, y: e.clientY - $pos2.y }; }
function move(e) {
if (!dragging) return;
pos2.set({ x: e.clientX - offset.x, y: e.clientY - offset.y }, { hard: true });
}
function up() { dragging = false; pos2.set({ x: 0, y: 0 }); } // springs back
</script>
<svelte:window on:mousemove={move} on:mouseup={up} />
<div class="draggable" style="transform: translate({$pos2.x}px, {$pos2.y}px)"
on:mousedown={down}>Drag me</div>
<!-- 11) Page transitions with motion -->
<!-- For full-page transitions, use Svelte's transition directives + motion stores together. -->
<!-- 12) Performance tips -->
<!-- • Animate transform + opacity only — GPU compositing -->
<!-- • Limit concurrent springs/tweens; one per element max -->
<!-- • Set precision = 0.001 (or smaller) so springs don't run forever -->
<!-- • Disable on prefers-reduced-motion -->
<!-- • For huge lists, animate only what's in view -->
<!-- 13) Reduced motion -->
<script>
import { onMount } from 'svelte';
let reduced = false;
onMount(() => { reduced = matchMedia('(prefers-reduced-motion: reduce)').matches; });
$: progressDuration = reduced ? 0 : 400;
</script>
<!-- 14) Common bugs -->
<!-- • Tweening width/height/margin → layout thrash; tween transform/scale -->
<!-- • Forgot $ prefix → store object instead of value used in template -->
<!-- • Spring with stiffness too low → animation drags forever; tune until natural -->
<!-- • Animating list items without keyed each → identity lost; FLIP-style movement breaks -->
<!-- • Hard set inside spring then expecting smooth → use { hard: true } only for jumps -->
<!-- • Spring with no precision → runs forever near target; set precision -->
<!-- • prefers-reduced-motion ignored → accessibility issue for vestibular sensitivity -->
Why it matters
tweened and spring from svelte/motion give you smooth animated values as reactive stores — set a target, the value interpolates. Animate transform/opacity for GPU performance, set precision so springs stop, and check prefers-reduced-motion for accessibility.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { spring, tweened } from 'svelte/motion';
const x = spring(0, { stiffness: 0.1 });
x.set(100);
Try it Yourself »
Discussion
Loading…