Since this chapter we shift to more in-depth Svelte themes. In this part we’ll cover visual tooling that comes from Svelte. It is really great that you can have beauty things right out of the box, without reinventing the wheel and with no need for extra tooling.
Motion
Functions that produce store-like value that changes in a way to have a smooth & good looking behaviour. Basically, it calculates all required values and emits them in the required interval of time (it has something more under the hood, this description is rather just to catch the idea).
Tweened
<script>
import { tweened } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
const progress = tweened(0, { // second param is optional
duration: 400,
easing: cubicOut,
delay: ..., // ms
easing: ..., // p => t
duration: ..., // (from, to) => milliseconds
interpolate: ..., // (from, to) => t => value
});
</script>
...
progress.set(50); // or progress.update(v => v + 1);
...
<input bind:value={progress} type="range" />
Spring
The spring
function is an alternative to tweened
that often works better for values that are frequently changing.
<script>
import { spring } from 'svelte/motion';
let coords = spring({ x: 50, y: 50 }, {
stiffness: 0.1,
damping: 0.25
});
</script>
...
<input bind:value={coords.stiffness} /> // damping/stiffness update is possible later
<circle cx={$coords.x} cy={$coords.y} /> // operate then as with regular store
Transition
Differs from motion as motion it rather about target value, but transition is rather about target DOM Object.
<script>
import { fade, fly } from 'svelte/transition';
let visible = true;
</script>
<input type="checkbox" bind:checked={visible} /> // <p /> will fade in/out
{#if visible}
<p transition:fade>Fades in and out</p>
<p transition:fly={{ y: 200, duration: 2000 }}>Params are optional</>
<p in:fly={{ y: 200, duration: 2000 }} out:fade>Flies in, fades out</p> // diferent in, out
{/if}
...
If
block + transition = ❤️
Remember times when you hide something with your framework via if-else templating and this block just disappears instantly as a ninja because it literally is cut from DOM? Forget about this in Svelte (at least, in this case) — it takes care and hides/shows the element in transitioned, expected manner. If there are several blocks, you may need to use global transition that is described next.
Global transitions — transition for any element
Add |global when you need to apply transition when any block containing the transitions is added or removed. E.g. toggling the visibility of the entire list does not apply transitions to individual list elements.
{#if showItems}
{#each items.slice(0, i) as item}
<div transition:slide|global> // when showItems changes, global still makes transition to work as expected
{item}
</div>
{/each}
{/if}
{#key}
Key blocks — recreate children’s DOM when an expression changes
When you need to have transition not only when element enters/leaves the DOM, but when some value changes.
{#key i} // when I changes, inside stuff recreates. As a result in: transition is shown when fresh 'i' comes
<p in:typewriter={{ speed: 10 }}>
{messages[i] || ''}
</p>
{/key}
Transition events
Usage as regular DOM event:
<div
transition:fly={{ y: 200, duration: 2000 }}
on:introstart={() => status = 'intro started'}
on:outrostart={() => status = 'outro started'}
on:introend={() => status = 'intro ended'}
on:outroend={() => status = 'outro ended'}
/>
Custom transitions — write own functions if default set is not enough
To have something custom you need to build a function that meets transition functions contract. Attaching here samples from official tutorial:
<script>
import { fade } from 'svelte/transition';
import { elasticOut } from 'svelte/easing';
let visible = true;
// Custom CSS (css property is returned)
function spin(node, { duration }) {
return {
duration,
css: (t) => {
const eased = elasticOut(t);
return `
transform: scale(${eased}) rotate(${eased * 1080}deg);
color: hsl(
${Math.trunc(t * 360)},
${Math.min(100, 1000 * (1 - t))}%,
${Math.min(50, 500 * (1 - t))}%
);`;
}
};
}
// Custom JS (tick property is returned)
function typewriter(node, { speed = 1 }) {
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = Math.trunc(text.length * t);
node.textContent = text.slice(0, i);
}
};
</script>
...
<div out:fade ... />
<p transition:typewriter ... />
Deferred transitions — when you need a smooth transit between different host components
This feature looks really cool. DOM elements literally fly out from on parent component into another. To achieve it you need to define a crossfade
function and then assign its output to target object.
The crossfade
function creates a pair of transitions called send
and receive
. Then, its output is assigned to desired markup element that is expected to be moved across:
export const [send, receive] = crossfade({...});
...
<li
class:done
in:receive={{ key: todo.id }}
out:send={{ key: todo.id }}
>
When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the fallback
transition is used.
Animation
<script>
import { flip } from 'svelte/animate';
...
</script>
...
<li
...
animate:flip // animate:flip={{ duration: 200 }} | animate:flip={{ duration: d => millis }}
>
We’ve covered the main topics regarding the theme, still it is rather an overview what exists in the toolbox and to master this properly will require some extra efforts.
Take care, go Svelte!
Resources
- Svelte Tutorial | Motions, Transitions, Animations
- Svelte transition docs
Top comments (0)