Svelte makes it very easy to work with transitions with the transition directive. We use the svelte/transition module for accomplishing anything related to transitions. See https://svelte.dev/docs/svelte/svelte-transition.
ex.
<script>
import { fade } from 'svelte/transition';
let visible = $state(true);
</script>
<label>
<input type="checkbox" bind:checked={visible} />
visible
</label>
{#if visible}
<p transition:fade>
Fades in and out
</p>
{/if}Transition functions can accept parameters too.
ex.
<script>
import { fly } from 'svelte/transition';
let visible = $state(true);
</script>
<label>
<input type="checkbox" bind:checked={visible} />
visible
</label>
{#if visible}
<p transition:fly={{ y:200, duration:2000 }}>
Fades in and out
</p>
{/if}The transition is reversible - if you toggle the checkbox while the transition is ongoing, it transitions from the current point rather than the beginning or the end.
In and out
Instead of the transition directive, an element can have an in or an out directive, or both together.
ex.
{#if visible}
<p in:fly={{ y: 200, duration: 2000 }} out:fade>
Flies in and out
</p>
{/if}In this case, the transitions are not reversed.
Custom CSS transitions
The svelte/transition module has a handful of builtin transitions, but ot’s very easy to create your own.
For example, the source for the fade transition is given below:
function fade(node, { delay = 0, duration = 400 }) {
const o = +getComputedStyle(node).opacity;
return {
delay,
duration,
css: (t) => `opacity: ${t * o}`
};
}THe function takes two arguments - the node on which the transition is applied, and the parameters passed in. It returns a transition object which can have the following properties:
delay: milliseconds before the transition beginsduration: the length of the transitioneasing: ap => teasing function (I don’t know what this means, the docs recommend to check out Tweening)css: a(t, u) => cssfunction whereu === 1-ttick: a(t, u) => {...}function that has some effect on the node
The t value is 0 at the beginning of an intro or end of an outro, while it’s 1 at the end of an intro or beginning of an outro.
Most of the time, you should return the css property and not the tick property, as CSS animations run off the main thread to prevent jank whenever possible.
Custom JS Transitions
The transition of a typewriter effect:
<script>
let visible = $state(false);
function typewriter(node, { speed = 1 }) {
const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
if (!valid) {
throw new Error(`This transition only works on elements with a single text node child`);
}
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)
}
}
return {};
}
</script>
<label>
<input type="checkbox" bind:checked={visible} />
visible
</label>
{#if visible}
<p transition:typewriter>
The quick brown fox jumps over the lazy dog
</p>
{/if}Transition events
It can be useful to know when transitions are beginning and ending. Svelte dispatches events that you can listen to like any other DOM event.
ex.
{#if visible}
<p
transition:fly={{ y: 200, duration: 2000 }}
onintrostart={() => status = 'intro started'}
onoutrostart={() => status = 'outro started'}
onintroend={() => status = 'intro ended'}
onoutroend={() => status = 'outro ended'}
>
Flies in and out
</p>
{/if}Global transitions
Ordinarily, transitions would only play on elements when thier direct containing block is added or removed. If we want to play transitions not only on individual item addition or removal, but for all items, we can achieve that using a global transition. It plays when any block containing the transitions is added or removed.
ex.
<script>
import { slide } from 'svelte/transition';
let items = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
let showItems = $state(true);
let i = $state(5);
</script>
<label>
<input type="checkbox" bind:checked={showItems} />
show list
</label>
<label>
<input type="range" bind:value={i} max="10" />
</label>
{#if showItems}
{#each items.slice(0, i) as item}
<div transition:slide|global>
{item}
</div>
{/each}
{/if}Key blocks
Key blocks destroy and recreate their contents when the value of an expression changes. This is useful if you want an element to play its transition whenever a value changes instead of when the elemenet enters or leaves the DOM.
ex.
<script>
import { typewriter } from './transition.js';
import { messages } from './loading-messages.js';
let i = $state(-1);
$effect(() => {
const interval = setInterval(() => {
i += 1;
i %= messages.length;
}, 2500);
return () => {
clearInterval(interval);
};
});
</script>
<h1>loading...</h1>
{#key i}
<p in:typewriter={{ speed: 10 }}>
{messages[i] || ''}
</p>
{/key}