DEV Community

Cover image for Svelte journey | Motion, Transitions, Animations
Denys Sych
Denys Sych

Posted on • Updated on

Svelte journey | Motion, Transitions, Animations

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"   />
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
...
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

{#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}
Enter fullscreen mode Exit fullscreen mode

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'}
/>
Enter fullscreen mode Exit fullscreen mode

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 ... />
Enter fullscreen mode Exit fullscreen mode

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 }}
>
Enter fullscreen mode Exit fullscreen mode

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 }}
>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)