DEV Community

Stefan Buhrmester
Stefan Buhrmester

Posted on • Updated on

Fluid page transitions with Svelte & Sapper

UPDATE: This post is now over 2 years old, but it's still kinda relevant. This technique should work exactly the same with SvelteKit.

Ever come across websites with cool animations whenever navigating from one page to another? For example this website by Sarah Drasner. Usually, you'd use client-side libraries and clever CSS tricks to achieve these sort of things. Probably nothing wrong with that, but it adds to your website's overall download size, and your CSS will become hard to maintain as your site grows in complexity. However, thanks to Svelte, we can now do these cool things without any client-side library or CSS trickery:

GREAT SUCCESS
(Try it out)

So now we can have cool PWAs, that are server-side rendered, have smooth page transitions, and are compiled down to really tiny amounts of javascript. With no need for client-side libraries, virtual DOMs, or shuffling CSS classes around. Oouwee!

And with that, our lives are now complete. Because what else could we possibly want? We should now all go and praise Rich Harris the JavaScript God. Or do we need more JavaScript frameworks? Don't we have enough? I can't take it anymore. Make it stop. Aaaaaahhh this is the end.

How?

These animations all use Svelte's crossfade transition. If you haven't used it before, I suggest checking out the tutorial. It will teach you how to crossfade two elements within the same component. But we are interested in crossfading an element from one component into another (in Sapper, each page is a component). The trick to making the crossfade transition work across different pages (or components) defined in separate files, is to create the crossfade transition in its own module/file, and import it into the different components. Check it out:

Here we create the crossfade transition in its own file and export the send and receive functions so that we can reuse them in different files.

// crossfade.js
import { quintOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition';
const [send, receive] = crossfade({
    duration: d => Math.sqrt(d * 300),
    fallback(node, params) {
        const style = getComputedStyle(node);
        const transform = style.transform === 'none' ? '' : style.transform;

        return {
            duration: 600,
            easing: quintOut,
            css: t => `
                transform: ${transform} scale(${t});
                opacity: ${t}
            `
        };
    }
});

export {send, receive};
Enter fullscreen mode Exit fullscreen mode

And then we use the send and receive function.

// page1.svelte
<script>
  import {send, receive} from './crossfade.js';
</script>

<main>
  <h1 out:send="{{key: 'h1'}}" in:receive="{{key: 'h1'}}">Look, I'm above the image</h1>
  <img out:send="{{key: 'borat'}}" in:receive="{{key: 'borat'}}" alt='Borat' src='great-success.png'>
</main>

Enter fullscreen mode Exit fullscreen mode

And then use the same send and receive function again, making the crossfade work across components.

// page2.svelte
<script>
  import {send, receive} from './crossfade.js';
</script>

<main>
  <img out:send="{{key: 'borat'}}" in:receive="{{key: 'borat'}}" alt='Borat' src='great-success.png'>
  <h1 out:send="{{key: 'h1'}}" in:receive="{{key: 'h1'}}">Now I am below the image</h1>
</main>

Enter fullscreen mode Exit fullscreen mode

The container needs to be absolutely positioned, otherwise it won't work.

// global.css
main {
  position: absolute;
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

There you go. Thanks for reading.

Visit https://buhrmi.dev if you'd like professional support.

Latest comments (10)

Collapse
 
jeffalo profile image
jeffalo

Hey, is there something similar for SvelteKit? It seems like SvelteKit's layout system works differently.

Collapse
 
buhrmi profile image
Stefan Buhrmester

Sorry for the late reply. It should work exactly the same for SvelteKit

Collapse
 
jeffalo profile image
jeffalo

Thanks, it looks like there's a SvelteKit bug that causes the page to get duplicated. I'll check again though.

Collapse
 
sino_thomas profile image
Sino Thomas

Very interesting.
Got it to work in Svelte REPL: svelte.dev/repl/e5e3027d27cf4440a1...

Collapse
 
franz profile image
Franz Sittampalam

this is hot, thanks for sharing

Collapse
 
giorgosk profile image
Giorgos Kontopoulos 👀

Stefan, this is great stuff thanks for posting

Collapse
 
stunjiturner profile image
S Tunji Turner

thanks for the post, I am having issues with the crossfade.js during deployment to now. the node_modules folder is in the .nowignore file as best practices? any ideas?

Collapse
 
doppelganger9 profile image
David Lacourt

Hi, thanks for providing the changes and some examples.
It can lead to great effects in the UI, and it seems to be easy to achieve with what you've done.
I'm curious where did you get the idea? some other framework/library?

Collapse
 
buhrmi profile image
Stefan Buhrmester

No, I was looking at this site and thought it'd be cool if there was a way to achieve this sort of effect with built-in Svelte tools only.

Collapse
 
doppelganger9 profile image
David Lacourt

Okay, thanks!

This kind of animations are so cool!

If I follow the "inspiration chain", I see the influence of Sarah Drasner's work, I knew it ;-)

I once saw her live, back in 2017, and my mind was blown 🤯: