Die eingebauten Transitions decken die meisten Standard-Bedürfnisse ab. Wenn du etwas Eigenes brauchst — eine Schreibmaschinen-Animation, einen Farbwechsel, eine Kombination aus mehreren Effekten — schreibst du dir eine Custom Transition. Das ist einfacher, als es klingt: Eine Transition ist im Grunde nur eine Funktion mit einer bestimmten Signatur. Dieser Artikel zeigt beide Wege (CSS-basiert und JavaScript-basiert) mit verständlichen Beispielen.

Die Signatur einer Custom Transition

Eine Transition ist eine Funktion, die zwei Argumente bekommt:

  1. Den DOM-Knoten (das Element, auf das sie angewendet wird).
  2. Ein Parameter-Objekt (das, was du in transition:fn={...} mitgibst).

Sie gibt ein Objekt zurück, das beschreibt, wie die Animation aussieht. Es gibt zwei Varianten — css und tick.

ts Skelett einer Custom Transition
function myTransition(node, params) {
    return {
        delay: 0,
        duration: 400,
        easing: (t) => t,
        css: (t, u) => `…`,    // Variante 1
        tick: (t, u) => { … }, // Variante 2
    };
}

Erklärung der t/u-Parameter:

  • t geht von 0 bis 1 während der Animation (Fortschritt).
  • u ist 1 - t (Restweg).
  • Bei einer **Out-**Transition geht t von 1 runter auf 0 (umgekehrte Richtung).

Das ist alles, was Svelte braucht. Den Rest macht die Engine.

Variante 1: css — performant via CSS-Strings

Die css-Variante ist die bevorzugte Form. Du gibst einen CSS-String zurück, Svelte erzeugt daraus eine echte CSS-Keyframe-Animation. Die läuft im Browser-Compositor — also off-thread und sehr performant.

ts src/lib/transitions/spin.ts
export function spin(node, { duration = 400 } = {}) {
    return {
        duration,
        css: (t) => {
            const eased = t;
            return `
                transform: scale(${eased}) rotate(${eased * 360}deg);
                opacity: ${eased};
            `;
        },
    };
}
svelte Verwendung
<script>
    import { spin } from '$lib/transitions/spin';
    let visible = $state(true);
</script>

{#if visible}
    <div transition:spin>Spin!</div>
{/if}

Die Funktion css(t, u) wird intern in eine CSS-Keyframe-Animation übersetzt. Das ist deutlich schneller als JavaScript-basierte Animation per Frame, weil der Browser den Kompositor nutzen kann.

Variante 2: tick — für DOM-Manipulation pro Frame

Wenn du etwas animieren willst, was sich nicht über CSS abbilden lässt — etwa den Inhalt eines Elements verändern, Canvas-Zeichnen oder DOM-Attribute setzen — gibt es tick. Diese Funktion läuft pro Animations-Frame.

ts src/lib/transitions/typewriter.ts
export function typewriter(node, { speed = 50 } = {}) {
    const text = node.textContent;
    const duration = text.length * speed;

    return {
        duration,
        tick: (t) => {
            const i = Math.floor(text.length * t);
            node.textContent = text.slice(0, i);
        },
    };
}
svelte Verwendung
<script>
    import { typewriter } from '$lib/transitions/typewriter';
    let visible = $state(true);
</script>

{#if visible}
    <p in:typewriter>Hallo, ich werde getippt.</p>
{/if}

Was hier passiert:

  1. Beim Start liest die Funktion den vollständigen Text aus dem DOM-Knoten.
  2. Bei jedem Frame berechnet sie, wie viel des Textes schon „getippt” sein sollte (t * length).
  3. Sie schreibt den Substring zurück ins DOM.

CSS könnte das nicht — es kann keinen Textinhalt verändern. tick ist hier zwingend.

Beide Varianten kombinieren

Du darfst css und tick zusammen zurückgeben. CSS macht das Optische, tick setzt zum Schluss noch DOM-Attribute auf. Das wird selten gebraucht, ist aber möglich.

Praxis-Beispiel: Background-Color-Fade

Eine Transition, die nicht nur Opazität fadet, sondern auch die Hintergrundfarbe von neutral auf Akzent wechselt — etwa zum Hervorheben einer neu eingegangenen Nachricht.

ts src/lib/transitions/highlight.ts
export function highlight(node, { color = '#fff7c2', duration = 800 } = {}) {
    const original = getComputedStyle(node).backgroundColor;
    return {
        duration,
        css: (t, u) => `
            background-color: color-mix(in srgb, ${color} ${u * 100}%, ${original});
        `,
    };
}
svelte Verwendung in Liste
<script>
    import { highlight } from '$lib/transitions/highlight';

    let messages = $state([]);
</script>

<ul>
    {#each messages as msg (msg.id)}
        <li in:highlight>{msg.text}</li>
    {/each}
</ul>

u ist 1 - t, läuft also von 1 runter auf 0. So beginnen wir mit dem Highlight-Color und faden schrittweise zum Original-Hintergrund.

Easing-Funktionen einsetzen

Die optionale easing-Funktion mappt t (linear 0→1) auf einen anderen Verlauf — z. B. „erst langsam, dann schneller”. Standardmäßig ist easing linear.

ts Mit eigenem Easing
import { cubicInOut } from 'svelte/easing';

export function spin(node, { duration = 400 } = {}) {
    return {
        duration,
        easing: cubicInOut,
        css: (t) => `transform: rotate(${t * 360}deg)`,
    };
}

Wer easing setzt, bekommt das gemappte t in der css/tick-Funktion. Aus t = 0.5 wird so vielleicht 0.62 — ein realistischeres Bewegungsgefühl.

Asynchrones Setup: Funktion gibt Funktion zurück

Manche Transitions brauchen Setup-Zeit vor der eigentlichen Animation — etwa um eine Größe zu messen oder ein Bild zu laden. Dafür gibst du eine Funktion zurück, die ihrerseits das Transition-Objekt zurückgibt:

ts Asynchrones Setup
export function expand(node) {
    const width = node.scrollWidth;
    return () => ({
        duration: 400,
        css: (t) => `width: ${t * width}px`,
    });
}

Svelte ruft die äußere Funktion direkt beim Mount auf — du kannst Werte messen, bevor die Animation startet. Die innere Funktion wird kurz vor der Animation aufgerufen — ideal für asynchrones Setup.

Das brauchst du selten, ist aber gut zu kennen.

Häufige Stolperfallen

css und tick zur Laufzeit gewechselt. Du gibst beim Setup zurück, was du nutzen willst. Während der Animation lässt sich nicht zwischen den Varianten wechseln.

duration als Pflicht-Property vergessen. Ohne duration weiß Svelte nicht, wie lange die Animation laufen soll. Default-Wert setzen oder Pflicht-Parameter erzwingen.

tick statt css, obwohl CSS reicht. tick ist langsamer, weil JavaScript jeden Frame ausgeführt wird. Wenn der Effekt mit Transform/Opacity/Filter darstellbar ist, immer css nehmen.

Element-Position innerhalb von tick falsch. Wer in tick getBoundingClientRect() aufruft und dann das DOM ändert, riskiert Layout-Thrashing. Solche Operationen lieber im Setup-Phase machen, nicht pro Frame.

Out-Transition mit t vorwärts erwartet. Bei einer Out-Animation läuft t rückwärts (von 1 auf 0). Wenn deine Berechnung das nicht berücksichtigt, sieht die Animation rückwärts seltsam aus. Im Zweifel mit t und u testen, was wann gilt.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu Transitions & Animations

Zur Übersicht