Skip to content

Commit 0b27a96

Browse files
authored
Motion tutorials (#949)
* update tween exercise * update spring tutorial
1 parent b0165dd commit 0b27a96

File tree

17 files changed

+177
-132
lines changed

17 files changed

+177
-132
lines changed

apps/svelte.dev/content/tutorial/02-advanced-svelte/03-stores/01-introducing-stores/index.md renamed to apps/svelte.dev/content/tutorial/02-advanced-svelte/01-advanced-reactivity/05-stores/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Introducing stores
2+
title: Stores
33
---
44

55
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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
let progress = $state(0);
3+
</script>
4+
5+
<progress value={progress}></progress>
6+
7+
<button onclick={() => (progress = 0)}>
8+
0%
9+
</button>
10+
11+
<button onclick={() => (progress = 0.25)}>
12+
25%
13+
</button>
14+
15+
<button onclick={() => (progress = 0.5)}>
16+
50%
17+
</button>
18+
19+
<button onclick={() => (progress = 0.75)}>
20+
75%
21+
</button>
22+
23+
<button onclick={() => (progress = 1)}>
24+
100%
25+
</button>
26+
27+
<style>
28+
progress {
29+
display: block;
30+
width: 100%;
31+
}
32+
</style>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script>
2+
import { Tween } from 'svelte/motion';
3+
import { cubicOut } from 'svelte/easing';
4+
5+
let progress = new Tween(0, {
6+
duration: 400,
7+
easing: cubicOut
8+
});
9+
</script>
10+
11+
<progress value={progress.current}></progress>
12+
13+
<button onclick={() => (progress.target = 0)}>
14+
0%
15+
</button>
16+
17+
<button onclick={() => (progress.target = 0.25)}>
18+
25%
19+
</button>
20+
21+
<button onclick={() => (progress.target = 0.5)}>
22+
50%
23+
</button>
24+
25+
<button onclick={() => (progress.target = 0.75)}>
26+
75%
27+
</button>
28+
29+
<button onclick={() => (progress.target = 1)}>
30+
100%
31+
</button>
32+
33+
<style>
34+
progress {
35+
display: block;
36+
width: 100%;
37+
}
38+
</style>

apps/svelte.dev/content/tutorial/02-advanced-svelte/03-stores/02-tweens/index.md renamed to apps/svelte.dev/content/tutorial/02-advanced-svelte/03-motion/01-tweens/index.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,53 @@
22
title: Tweened values
33
---
44

5-
Alongside the `writable` and `readable` stores, Svelte ships stores for adding motion to your user interfaces.
5+
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.
66

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

99
```svelte
1010
/// file: App.svelte
1111
<script>
12-
import { +++tweened+++ } from 'svelte/+++motion+++';
12+
+++import { Tween } from 'svelte/motion';+++
1313
14-
const progress = +++tweened+++(0);
14+
let progress = $state(0);
1515
</script>
1616
```
1717

18+
Turn `progress` into an instance of `Tween`:
19+
20+
```svelte
21+
/// file: App.svelte
22+
<script>
23+
import { Tween } from 'svelte/motion';
24+
25+
let progress = +++new Tween+++(0);
26+
</script>
27+
```
28+
29+
The `Tween` class has a writable `target` property and a readonly `current` property — update the `<progress>` element...
30+
31+
```svelte
32+
<progress value={progress.+++current+++}></progress>
33+
```
34+
35+
...and each of the event handlers:
36+
37+
```svelte
38+
<button onclick={() => (progress.+++target+++ = 0)}>
39+
0%
40+
</button>
41+
```
42+
1843
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:
1944

2045
```svelte
2146
/// file: App.svelte
2247
<script>
23-
import { tweened } from 'svelte/motion';
48+
import { Tween } from 'svelte/motion';
2449
+++import { cubicOut } from 'svelte/easing';+++
2550
26-
const progress = tweened(0, +++{
51+
let progress = new Tween(0, +++{
2752
duration: 400,
2853
easing: cubicOut
2954
}+++);
@@ -32,11 +57,11 @@ Clicking the buttons causes the progress bar to animate to its new value. It's a
3257

3358
> [!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.
3459
35-
The full set of options available to `tweened`:
60+
The full set of options available to `Tween`:
3661

3762
- `delay` — milliseconds before the tween starts
3863
- `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
3964
- `easing` — a `p => t` function
4065
- `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
4166

42-
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.
67+
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.
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
<script>
2-
import { writable } from 'svelte/store';
3-
4-
let coords = writable({ x: 50, y: 50 });
5-
let size = writable(10);
2+
let coords = $state({ x: 50, y: 50 });
3+
let size = $state(10);
64
</script>
75

86
<svg
97
onmousemove={(e) => {
10-
coords.set({ x: e.clientX, y: e.clientY });
8+
coords = { x: e.clientX, y: e.clientY };
119
}}
12-
onmousedown={() => size.set(30)}
13-
onmouseup={() => size.set(10)}
10+
onmousedown={() => (size = 30)}
11+
onmouseup={() => (size = 10)}
1412
role="presentation"
1513
>
1614
<circle
17-
cx={$coords.x}
18-
cy={$coords.y}
19-
r={$size}
20-
/>
15+
cx={coords.x}
16+
cy={coords.y}
17+
r={size}
18+
></circle>
2119
</svg>
2220

2321
<div class="controls">
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
<script>
2-
import { spring } from 'svelte/motion';
2+
import { Spring } from 'svelte/motion';
33
4-
let coords = spring({ x: 50, y: 50 }, {
4+
let coords = new Spring({ x: 50, y: 50 }, {
55
stiffness: 0.1,
66
damping: 0.25
77
});
88
9-
let size = spring(10);
9+
let size = new Spring(10);
1010
</script>
1111

1212
<svg
1313
onmousemove={(e) => {
14-
coords.set({ x: e.clientX, y: e.clientY });
14+
coords.target = { x: e.clientX, y: e.clientY };
1515
}}
16-
onmousedown={() => size.set(30)}
17-
onmouseup={() => size.set(10)}
16+
onmousedown={() => (size.target = 30)}
17+
onmouseup={() => (size.target = 10)}
1818
role="presentation"
1919
>
2020
<circle
21-
cx={$coords.x}
22-
cy={$coords.y}
23-
r={$size}
21+
cx={coords.current.x}
22+
cy={coords.current.y}
23+
r={size.current}
2424
/>
2525
</svg>
2626

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: Springs
3+
---
4+
5+
The `Spring` class is an alternative to `Tween` that often works better for values that are frequently changing.
6+
7+
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:
8+
9+
```svelte
10+
/// file: App.svelte
11+
<script>
12+
+++import { Spring } from 'svelte/motion+++';
13+
14+
let coords = +++new Spring+++({ x: 50, y: 50 });
15+
let size = +++new Spring+++(10);
16+
</script>
17+
```
18+
19+
As with `Tween`, springs have a writable `target` property and a readonly `current` property. Update the event handlers...
20+
21+
```svelte
22+
<svg
23+
onmousemove={(e) => {
24+
coords.+++target+++ = { x: e.clientX, y: e.clientY };
25+
}}
26+
onmousedown={() => (size.+++target+++ = 30)}
27+
onmouseup={() => (size.+++target+++ = 10)}
28+
role="presentation"
29+
>
30+
```
31+
32+
...and the `<circle>` attributes:
33+
34+
```svelte
35+
<circle
36+
cx={coords.+++current+++.x}
37+
cy={coords.+++current+++.y}
38+
r={size.+++current+++}
39+
></circle>
40+
```
41+
42+
Both springs have default `stiffness` and `damping` values, which control the spring's, well... springiness. We can specify our own initial values:
43+
44+
```js
45+
/// file: App.svelte
46+
let coords = new Spring({ x: 50, y: 50 }, +++{
47+
stiffness: 0.1,
48+
damping: 0.25
49+
}+++);
50+
```
51+
52+
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 numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Stores
2+
title: Motion
33
scope: { 'prefix': '/src/lib/', 'name': 'src' }
44
focus: /src/lib/App.svelte
55
---

apps/svelte.dev/content/tutorial/02-advanced-svelte/03-stores/02-tweens/+assets/app-a/src/lib/App.svelte

Lines changed: 0 additions & 34 deletions
This file was deleted.

apps/svelte.dev/content/tutorial/02-advanced-svelte/03-stores/02-tweens/+assets/app-b/src/lib/App.svelte

Lines changed: 0 additions & 38 deletions
This file was deleted.

apps/svelte.dev/content/tutorial/02-advanced-svelte/03-stores/03-springs/index.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

apps/svelte.dev/src/routes/tutorial/[...slug]/+page.server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ const redirects = new Map([
99
['reactive-statements', 'svelte/effects'],
1010
['updating-arrays-and-objects', 'svelte/deep-state'],
1111
['event-modifiers', 'svelte/capturing'],
12-
['dom-event-forwarding', 'svelte/spreading-events']
12+
['dom-event-forwarding', 'svelte/spreading-events'],
13+
['svelte/introducing-stores', 'stores']
1314
]);
1415

1516
export async function load({ params }) {

0 commit comments

Comments
 (0)