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

Animations

Svelte’s animate: directive smoothly animates LAYOUT changes — reorders, inserts, deletions — using FLIP. Combined with tweened/spring stores and CSS transitions you get production-quality motion in tiny diffs.

flip, tweened, spring, motion patterns

EXAMPLE
<script>
import { flip } from 'svelte/animate';
import { fade, fly, slide } from 'svelte/transition';
import { tweened, spring } from 'svelte/motion';
import { cubicOut, elasticOut } from 'svelte/easing';

let items = [
    { id: 1, text: 'apple' },
    { id: 2, text: 'banana' },
    { id: 3, text: 'cherry' },
];

function shuffle() {
    items = [...items].sort(() => Math.random() - 0.5);
}
function remove(id) {
    items = items.filter((i) => i.id !== id);
}
function add() {
    items = [...items, { id: Date.now(), text: 'new ' + items.length }];
}
</script>

<!-- 1) animate:flip — animates LAYOUT changes -->
<button on:click={shuffle}>Shuffle</button>
<button on:click={add}>Add</button>
<ul>
    {#each items as item (item.id)}
        <li animate:flip={{ duration: 300, easing: cubicOut }}>
            {item.text}
            <button on:click={() => remove(item.id)}>×</button>
        </li>
    {/each}
</ul>

<!-- FLIP = First, Last, Invert, Play — Svelte calculates the old/new positions and tweens. -->

<!-- 2) Combine FLIP with transitions on enter/leave -->
<ul>
    {#each items as item (item.id)}
        <li
            in:fly={{ y: -10, duration: 200 }}
            out:fade={{ duration: 150 }}
            animate:flip={{ duration: 300 }}
        >{item.text}</li>
    {/each}
</ul>

<!-- 3) tweened store — animate a numeric value -->
<script>
import { tweened } from 'svelte/motion';
const progress = tweened(0, { duration: 400, easing: cubicOut });
</script>

<button on:click={() => progress.set(Math.random())}>New target</button>
<progress value={$progress}></progress>
<p>{($progress * 100).toFixed(0)}%</p>

<!-- 4) Tweening a vector — multiple values together -->
<script>
const pos = tweened({ x: 0, y: 0 }, { duration: 300 });
function moveTo(x, y) { pos.set({ x, y }); }
</script>

<div style="transform: translate({$pos.x}px, {$pos.y}px)">Box</div>

<!-- 5) spring store — physics-based motion -->
<script>
const spr = spring(0, { stiffness: 0.1, damping: 0.4, precision: 0.001 });
function nudge() { spr.set($spr + 30); }
</script>

<div style="transform: translateX({$spr}px)">Springs</div>

<!-- spring overshoots + settles; great for UI gestures. -->

<!-- 6) Animated SVG path -->
<script>
import { writable } from 'svelte/store';
import { tweened } from 'svelte/motion';

const d = tweened(0, { duration: 800, easing: cubicOut });
let width = 100;
</script>

<svg viewBox="0 0 200 100">
    <rect x="0" y="40" width={$d} height="20" fill="steelblue" />
</svg>
<button on:click={() => d.set(width)}>Fill</button>
<button on:click={() => d.set(0)}>Empty</button>

<!-- 7) Coordinating transitions with crossfade -->
<script>
import { crossfade } from 'svelte/transition';
const [send, receive] = crossfade({
    duration: 200,
    fallback: (node) => ({ duration: 150, css: (t) => `opacity: ${t}` }),
});

let left  = [ { id:1, text:'a' }, { id:2, text:'b' } ];
let right = [];
function move(item, from, to) {
    from.splice(from.indexOf(item), 1); to.push(item);
    left = left; right = right;
}
</script>
<ul>{#each left as i (i.id)}<li in:receive={{ key: i.id }} out:send={{ key: i.id }} on:click={() => move(i, left, right)}>{i.text}</li>{/each}</ul>
<ul>{#each right as i (i.id)}<li in:receive={{ key: i.id }} out:send={{ key: i.id }} on:click={() => move(i, right, left)}>{i.text}</li>{/each}</ul>

<!-- 8) Custom animation function -->
<script>
function whoosh(node, { duration = 400 } = {}) {
    return {
        duration,
        css: (t) => `transform: scale(${0.95 + 0.05 * t}) translateY(${(1-t) * -10}px); opacity: ${t}`,
    };
}
</script>

{#if visible}
    <div in:whoosh>Custom enter</div>
{/if}

<!-- 9) Sequence with delay -->
{#each items as item, i (item.id)}
    <div in:fly={{ y: 20, delay: i * 80 }}>{item.text}</div>
{/each}

<!-- 10) Performance + UX -->
<!--   • All built-in transitions use CSS where possible (compositing-only, GPU-friendly) -->
<!--   • Use shallowRef-style data (assign new array, don't mutate) so Svelte detects changes -->
<!--   • Animate transform/opacity, not width/height — better FPS -->
<!--   • Respect prefers-reduced-motion -->

<script>
import { onMount } from 'svelte';
let reduceMotion = false;
onMount(() => {
    reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
});
</script>
{#if visible}
    <p transition:fade={{ duration: reduceMotion ? 0 : 300 }}>Adaptive</p>
{/if}

<!-- 11) Common bugs -->
<!--   • Items reorder but no animate:flip — list snaps -->
<!--   • Forgot keys in {#each} — FLIP can't track identity, animations misfire -->
<!--   • Transitioning width / height in tweened — layout thrash; use transform/scale -->
<!--   • Springs run forever — set 'precision' to stop near target -->
<!--   • prefers-reduced-motion ignored — bad UX for users with vestibular sensitivities -->
<!--   • crossfade fallback missing → elements vanish instantly when no matching pair -->
<!--   • Animation library + custom store both mutating dom node — racing -->

Why it matters

Svelte’s motion primitives (animate:flip, tweened, spring, crossfade) cover most UI animation without any third-party library. Combine FLIP for reorder with in:/out: transitions for enter/leave, animate transform + opacity only, and honour prefers-reduced-motion so vestibular-sensitive users aren’t left out.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<script>import { flip } from 'svelte/animate';</script>
{#each items as i (i.id)}<div animate:flip>{i.name}</div>{/each}
Try it Yourself »

Discussion

Loading…