Skip to content

Motion tutorials #949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Introducing stores
title: Stores
---

Prior to the introduction of runes in Svelte 5, stores were the idiomatic way to handle reactive state outside components. That's no longer the case, but you'll still encounter stores when using Svelte (including in SvelteKit, for now), so it's worth knowing how to use them.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script>
let progress = $state(0);
</script>

<progress value={progress}></progress>

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

<button onclick={() => (progress = 0.25)}>
25%
</button>

<button onclick={() => (progress = 0.5)}>
50%
</button>

<button onclick={() => (progress = 0.75)}>
75%
</button>

<button onclick={() => (progress = 1)}>
100%
</button>

<style>
progress {
display: block;
width: 100%;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script>
import { Tween } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
let progress = new Tween(0, {
duration: 400,
easing: cubicOut
});
</script>

<progress value={progress.current}></progress>

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

<button onclick={() => (progress.target = 0.25)}>
25%
</button>

<button onclick={() => (progress.target = 0.5)}>
50%
</button>

<button onclick={() => (progress.target = 0.75)}>
75%
</button>

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

<style>
progress {
display: block;
width: 100%;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,53 @@
title: Tweened values
---

Alongside the `writable` and `readable` stores, Svelte ships stores for adding motion to your user interfaces.
Often, a good way to communicate that a value is changing is to use _motion_. Svelte ships classes for adding motion to your user interfaces.

Let's start by changing the `progress` store to a `tweened` store:
Import the `Tween` class from `svelte/motion`:

```svelte
/// file: App.svelte
<script>
import { +++tweened+++ } from 'svelte/+++motion+++';
+++import { Tween } from 'svelte/motion';+++

const progress = +++tweened+++(0);
let progress = $state(0);
</script>
```

Turn `progress` into an instance of `Tween`:

```svelte
/// file: App.svelte
<script>
import { Tween } from 'svelte/motion';

let progress = +++new Tween+++(0);
</script>
```

The `Tween` class has a writable `target` property and a readonly `current` property — update the `<progress>` element...

```svelte
<progress value={progress.+++current+++}></progress>
```

...and each of the event handlers:

```svelte
<button onclick={() => (progress.+++target+++ = 0)}>
0%
</button>
```

Clicking the buttons causes the progress bar to animate to its new value. It's a bit robotic and unsatisfying though. We need to add an easing function:

```svelte
/// file: App.svelte
<script>
import { tweened } from 'svelte/motion';
import { Tween } from 'svelte/motion';
+++import { cubicOut } from 'svelte/easing';+++

const progress = tweened(0, +++{
let progress = new Tween(0, +++{
duration: 400,
easing: cubicOut
}+++);
Expand All @@ -32,11 +57,11 @@ Clicking the buttons causes the progress bar to animate to its new value. It's a

> [!NOTE] The `svelte/easing` module contains the [Penner easing equations](https://web.archive.org/web/20190805215728/http://robertpenner.com/easing/), or you can supply your own `p => t` function where `p` and `t` are both values between 0 and 1.

The full set of options available to `tweened`:
The full set of options available to `Tween`:

- `delay` — milliseconds before the tween starts
- `duration` — either the duration of the tween in milliseconds, or a `(from, to) => milliseconds` function allowing you to (e.g.) specify longer tweens for larger changes in value
- `easing` — a `p => t` function
- `interpolate` — a custom `(from, to) => t => value` function for interpolating between arbitrary values. By default, Svelte will interpolate between numbers, dates, and identically-shaped arrays and objects (as long as they only contain numbers and dates or other valid arrays and objects). If you want to interpolate (for example) colour strings or transformation matrices, supply a custom interpolator

You can also pass these options to `progress.set` and `progress.update` as a second argument, in which case they will override the defaults. The `set` and `update` methods both return a promise that resolves when the tween completes.
You can also call `progress.set(value, options)` instead of assigning directly to `progress.target`, in which case `options` will override the defaults. The `set` method returns a promise that resolves when the tween completes.
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
<script>
import { writable } from 'svelte/store';

let coords = writable({ x: 50, y: 50 });
let size = writable(10);
let coords = $state({ x: 50, y: 50 });
let size = $state(10);
</script>

<svg
onmousemove={(e) => {
coords.set({ x: e.clientX, y: e.clientY });
coords = { x: e.clientX, y: e.clientY };
}}
onmousedown={() => size.set(30)}
onmouseup={() => size.set(10)}
onmousedown={() => (size = 30)}
onmouseup={() => (size = 10)}
role="presentation"
>
<circle
cx={$coords.x}
cy={$coords.y}
r={$size}
/>
cx={coords.x}
cy={coords.y}
r={size}
></circle>
</svg>

<div class="controls">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
<script>
import { spring } from 'svelte/motion';
import { Spring } from 'svelte/motion';

let coords = spring({ x: 50, y: 50 }, {
let coords = new Spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});

let size = spring(10);
let size = new Spring(10);
</script>

<svg
onmousemove={(e) => {
coords.set({ x: e.clientX, y: e.clientY });
coords.target = { x: e.clientX, y: e.clientY };
}}
onmousedown={() => size.set(30)}
onmouseup={() => size.set(10)}
onmousedown={() => (size.target = 30)}
onmouseup={() => (size.target = 10)}
role="presentation"
>
<circle
cx={$coords.x}
cy={$coords.y}
r={$size}
cx={coords.current.x}
cy={coords.current.y}
r={size.current}
/>
</svg>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
title: Springs
---

The `Spring` class is an alternative to `Tween` that often works better for values that are frequently changing.

In this example we have a circle that follows the mouse, and two values — the circle's coordinates, and its size. Let's convert them to springs:

```svelte
/// file: App.svelte
<script>
+++import { Spring } from 'svelte/motion+++';

let coords = +++new Spring+++({ x: 50, y: 50 });
let size = +++new Spring+++(10);
</script>
```

As with `Tween`, springs have a writable `target` property and a readonly `current` property. Update the event handlers...

```svelte
<svg
onmousemove={(e) => {
coords.+++target+++ = { x: e.clientX, y: e.clientY };
}}
onmousedown={() => (size.+++target+++ = 30)}
onmouseup={() => (size.+++target+++ = 10)}
role="presentation"
>
```

...and the `<circle>` attributes:

```svelte
<circle
cx={coords.+++current+++.x}
cy={coords.+++current+++.y}
r={size.+++current+++}
></circle>
```

Both springs have default `stiffness` and `damping` values, which control the spring's, well... springiness. We can specify our own initial values:

```js
/// file: App.svelte
let coords = new Spring({ x: 50, y: 50 }, +++{
stiffness: 0.1,
damping: 0.25
}+++);
```

Waggle your mouse around, and try dragging the sliders to get a feel for how they affect the spring's behaviour. Notice that you can adjust the values while the spring is still in motion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Stores
title: Motion
scope: { 'prefix': '/src/lib/', 'name': 'src' }
focus: /src/lib/App.svelte
---

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const redirects = new Map([
['reactive-statements', 'svelte/effects'],
['updating-arrays-and-objects', 'svelte/deep-state'],
['event-modifiers', 'svelte/capturing'],
['dom-event-forwarding', 'svelte/spreading-events']
['dom-event-forwarding', 'svelte/spreading-events'],
['svelte/introducing-stores', 'stores']
]);

export async function load({ params }) {
Expand Down
Loading