Cours Svelte et SvelteKit · Module 9: Transitions et animations

Motion : Tween et Spring

2 min de lecture ·
Dans cette leçon4

Le module svelte/motion propose deux classes pour faire évoluer une valeur en douceur : Tween et Spring (depuis Svelte 5.8). Les deux ont deux champs importants :

  • target - la valeur visée (tu peux l'affecter),
  • current - la valeur actuelle animée (en lecture seule et réactive).

La méthode set(valeur, options) fixe aussi la cible et renvoie une Promise résolue à la fin de l'animation.

Tween - Transitions de valeurs fluides

<script>
  import { Tween } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  const progress = new Tween(0, {
    duration: 400,
    easing: cubicOut
  });
</script>

<button onclick={() => progress.target = 100}>100%</button>
<button onclick={() => progress.target = 0}>0%</button>

<progress value={progress.current} max="100"></progress>
<p>{Math.round(progress.current)}%</p>

<style>
  progress {
    width: 100%;
    height: 20px;
  }
</style>

Spring - Physique du ressort

<script>
  import { Spring } from 'svelte/motion';

  const coords = new Spring({ x: 50, y: 50 }, {
    stiffness: 0.1,  // rigidité
    damping: 0.5     // amortissement
  });

  function handleMouseMove(e) {
    coords.target = {
      x: e.clientX,
      y: e.clientY
    };
  }
</script>

<svelte:window onmousemove={handleMouseMove} />

<svg>
  <circle cx={coords.current.x} cy={coords.current.y} r="20" fill="gold" />
</svg>

Exemple : graphique animé

<script>
  import { Tween } from 'svelte/motion';

  const data = new Tween([10, 20, 30, 40, 50], { duration: 500 });

  function randomize() {
    data.target = Array(5).fill(0).map(() => Math.random() * 100);
  }
</script>

<button onclick={randomize}>Randomize</button>

<div class="chart">
  {#each data.current as value, i}
    <div class="bar" style="height: {value}%">
      {Math.round(value)}
    </div>
  {/each}
</div>

<style>
  .chart {
    display: flex;
    gap: 10px;
    height: 200px;
    align-items: flex-end;
  }
  .bar {
    flex: 1;
    background: gold;
    transition: height 0.3s;
  }
</style>

L'ancienne API : tweened et spring

Avant Svelte 5.8, on utilisait les fonctions tweened() et spring(), qui renvoient des stores lus avec $progress. Elles fonctionnent encore, mais elles sont marquées comme obsolètes (deprecated). Dans du nouveau code, choisis les classes Tween et Spring.

// Avant (store)
const progress = tweened(0);   // dans le template : {$progress}

// Svelte 5
const progress = new Tween(0); // dans le template : {progress.current}
Code de cette leçon : App.svelte
<script>
  // Motion : les classes Tween et Spring (Svelte 5)
  import { Tween, Spring } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  // Tween - interpolation fluide d’une valeur sur une durée donnée
  const progress = new Tween(0, {
    duration: 400,
    easing: cubicOut
  });

  // Spring - animation avec une physique de ressort
  const coords = new Spring({ x: 50, y: 50 }, {
    stiffness: 0.1,
    damping: 0.25
  });

  function handleMouseMove(e) {
    const rect = e.currentTarget.getBoundingClientRect();
    coords.set({
      x: e.clientX - rect.left,
      y: e.clientY - rect.top
    });
  }
</script>

<main class="motion-demo">
  <h1>Motion : Tween et Spring</h1>

  <section>
    <h2>Tween - Barre de progression</h2>
    <div class="progress-bar">
      <div class="fill" style="width: {progress.current}%"></div>
    </div>
    <p>{Math.round(progress.current)}%</p>
    <div class="buttons">
      <button onclick={() => progress.target = 0}>0%</button>
      <button onclick={() => progress.target = 50}>50%</button>
      <button onclick={() => progress.target = 100}>100%</button>
    </div>
  </section>

  <section>
    <h2>Spring - Élément suiveur</h2>
    <div class="spring-area" onmousemove={handleMouseMove}>
      <div
        class="follower"
        style="left: {coords.current.x}px; top: {coords.current.y}px"
      >
        ᛏ
      </div>
      <p class="hint">Déplace le curseur</p>
    </div>
  </section>
</main>

<style>
  .motion-demo {
    font-family: 'Georgia', serif;
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
    background: #1a0a0a;
    color: #f7d5e8;
    min-height: 100vh;
  }

  h1 {
    color: #ff6b6b;
    text-align: center;
  }

  section {
    background: rgba(255, 107, 107, 0.1);
    border: 1px solid #ff6b6b;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  h2 {
    color: #ff6b6b;
    font-size: 1rem;
    margin-top: 0;
  }

  .progress-bar {
    height: 30px;
    background: rgba(0,0,0,0.5);
    border-radius: 15px;
    overflow: hidden;
    margin-bottom: 0.5rem;
  }

  .fill {
    height: 100%;
    background: linear-gradient(90deg, #ff6b6b, #ffd700);
    border-radius: 15px;
  }

  .buttons {
    display: flex;
    gap: 0.5rem;
  }

  button {
    flex: 1;
    padding: 0.5rem;
    background: #3a1a2a;
    color: #f7d5e8;
    border: 1px solid #ff6b6b;
    border-radius: 4px;
    cursor: pointer;
  }

  .spring-area {
    position: relative;
    height: 200px;
    background: rgba(0,0,0,0.3);
    border-radius: 8px;
    overflow: hidden;
  }

  .follower {
    position: absolute;
    font-size: 2rem;
    transform: translate(-50%, -50%);
    pointer-events: none;
  }

  .hint {
    position: absolute;
    bottom: 10px;
    width: 100%;
    text-align: center;
    color: #a87ca8;
    font-size: 0.85rem;
  }
</style>

Entrées de la documentation utilisées dans cette leçon

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. En quoi Spring diffère-t-il de Tween ?

Articles utiles