Simple page transitions in Svelte
15 May 2020
Not yet updated to reflect migration to SvelteKit.
What we’re making
We’re going to replicate this very website’s page transition. It’s a technique adapted from this Stack Overflow thread.
We’ll again be using Sapper for this tutorial so go ahead and clone its starter template. Note that this technique isn’t exclusive to Sapper, it’s just what I’m comfortable with.
npx degit "sveltejs/sapper-template#rollup" my-app
cd my-app
npm install
# wait
npm run dev
The transition component
Create a transition component that you’ll wrap your routes with.
<script>
import { cubicInOut } from 'svelte/easing'
let duration = 200
let delay = duration
const transitionIn = () => ({
duration,
delay,
easing: cubicInOut,
css: (t) => `opacity: ${t}`,
})
const transitionOut = () => ({
duration,
delay: 0,
easing: cubicInOut,
css: (t) => `opacity: ${t}`,
})
</script>
<div in:transitionIn out:transitionOut>
<slot />
</div>
What each line of code does is out of the scope of this post. You can learn more from the official tutorial and/or doc.
Using the transition component
Wrap all of your routes (including _error.svelte
, but not _layout.svelte
) this way:
<script>
import PageTransition from '../components/PageTransition.svelte'
</script>
<style>
/* … */
</style>
<svelte:head>
<title>Sapper project template</title>
</svelte:head>
<PageTransition>
<h1>Great success!</h1>
<!-- … -->
</PageTransition>
For blog/[slug].svelte
, hardcode some links to test transition from one blog post to another.
<script context="module">
// …
</script>
<script>
import PageTransition from '../../components/PageTransition.svelte'
export let post
</script>
<style>
/* … */
</style>
<svelte:head>
<title>{post.title}</title>
</svelte:head>
<PageTransition>
<a href="/blog/what-is-sapper" rel="prefetch">Go to post A</a>
<a href="/blog/how-to-use-sapper" rel="prefetch">Go to post B</a>
<h1>{post.title}</h1>
<div class="content">{@html post.html}</div>
</PageTransition>
You should see that there is transition when you go from blog index to blog post, but not from post A to post B. We’ll fix this next.
Special treatment for dynamic routes
Think about what happens when you move between posts. The route component isn’t ‘destroyed and remounted’. Instead, the post
prop merely changes, causing:
title
andh1
to react to a newpost.title
, and- the markup inside
div.content
to react to a newpost.html
.
PageTransition
stays the same and its in
and out
transitions are not triggered.
You know that the posts differ, and their slug
is unique among them. Svelte’s way of assigning identity is through keyed each blocks. We’re going to use the post’s slug
as key.
Wrap the markup with this keyed each
block.
{#each [post] as p (p.slug)} <!-- add this -->
<PageTransition>
<a href="/blog/what-is-sapper" rel="prefetch">Go to post A</a>
<a href="/blog/how-to-use-sapper" rel="prefetch">Go to post B</a>
<h1>{p.title}</h1> <!-- change this -->
<div class="content">
{@html p.html} <!-- and this -->
</div>
</PageTransition>
{/each} <!-- and add this -->
each
takes an array. In this case we’re giving it an array of one (the post
prop), declaring slug
as its identity (key). When the identity changes (you navigate to another blog post), the whole tree is invalidated, causing transition to take place.