iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…