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.

TerminalShell
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.

src/components/PageTransition.svelteSvelte
<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:

src/routes/index.svelteSvelte
<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.

src/routes/blog/[slug].svelteSvelte
<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 and h1 to react to a new post.title, and
  • the markup inside div.content to react to a new post.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.

src/routes/blog/[slug].svelteSvelte
{#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.