Simple page transitions in Svelte
Ryandi Tjia — 15 May 2020
I recommend these articles if you wish to understand the rationale behind this intro:
Feel free to skip the intro altogether if you want to jump straight to the tutorial
#Intro: Svelte as an enabler
Web development is a monster of a subject. Some areas may feel like scary waters, others like steep hills. Many of its aspects I wouldn’t touch without reaching for a library. Even then, picking a library can be a struggle in itself.
Page transition is one I never bother with when I use React. Imagining having to:
- find out the features and differences,
- check the compatibility with Gatsby / Next.js,
- learn the API,
- actually use on project,
- and deal with the quirks
of:
- ReactTransitionGroup
- ReactCSSTransitionGroup
- react-spring
- react-motion
- Pose
- Framer Motion
straight up transitions me out of the mood. This is more a personal anecdote than a jab on React.
Svelte is no silver bullet but at times it gives me more confidence. This happens to be one of those times. And you know what they say — when you have confidence, you have more fun and are more likely to succeed.
#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.
#Outro: To hear from you
Reach out to me on Twitter @ryanditjia or email me at ryan@apostrof.co if you:
- think that I can improve on my code examples
- see that I’ve made a mistake
- wish to learn about something that I made (I made this post because someone reached out to me on Discord asking about this site’s page transition)
- have any issues with regards to accessing this website
- have something cool you want to show me
Thanks for reading, I hope this tutorial helps you add extra pizzazz to your project.