Svelte and SvelteKit course Β· Module 9: Transitions and Animations

Motion: Tween and Spring

2 min read Β·
In this lesson4

The svelte/motion module has two classes for changing values smoothly: Tween and Spring (since Svelte 5.8). Both have two important fields:

  • target - the value you are heading to (you can assign it),
  • current - the current, animated value (read-only and reactive).

The set(value, options) method also sets the target and returns a Promise that resolves when the animation finishes.

Tween - Smooth value transitions

<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 - Spring physics

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

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

  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>

Example: Animated chart

<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>

The older API: tweened and spring

Before Svelte 5.8 you used the tweened() and spring() functions, which return stores read with $progress. They still work, but they are marked as deprecated. In new code, choose the Tween and Spring classes.

// Before (store)
const progress = tweened(0);   // in the template: {$progress}

// Svelte 5
const progress = new Tween(0); // in the template: {progress.current}
Code for this lesson: App.svelte
<script>
  // Motion: the Tween and Spring classes (Svelte 5)
  import { Tween, Spring } from 'svelte/motion';
  import { cubicOut } from 'svelte/easing';

  // Tween - smooth value interpolation over a set time
  const progress = new Tween(0, {
    duration: 400,
    easing: cubicOut
  });

  // Spring - spring-physics animation
  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 and Spring</h1>

  <section>
    <h2>Tween - Progress bar</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 - Following element</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">Move cursor</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>

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How does Spring differ from Tween?

Useful articles