Demo: https://sveltekit-page-transitions.netlify.app/
Source code: https://github.com/evanwinter/sveltekit-page-transitions
Overview
Create a
<PageTransition />
component that does a simple fade-out-and-in transition (using Svelte's built-intransition
directive) when a given prop changes.Create a layout file, wrap its content with the
<PageTransition />
component, and pass the current page's path to the component as a prop.
Step 1: Creating the <PageTransition />
component
Create a component file at src/lib/components/PageTransition.svelte
.
Import the fly
transition from Svelte's built-in transition library, and setup an element that fly
s in and out. Add a delay
to the in
transition that's equal to or greater than the duration
of the out
transition (otherwise, the user will see them happen at the same time).
Export a variable called url
and wrap the element inside of a #{key ...}
block.
The contents of the block will be destroyed and recreated only when the value of url
changes.
When that happens, the out
transition will be executed immediately, and the in
transition happen once the out
transition finishes –– creating a "fade-out-and-back-in" effect.
<!-- PageTransition.svelte -->
<script>
import { fly } from "svelte/transition";
export let url = "";
const pageTransitionDuration = 500;
</script>
{#key url}
<div in:fly={{ x:-5, duration: pageTransitionDuration, delay: pageTransitionDuration }}
out:fly={{ x: 5, duration: pageTransitionDuration }}>
<slot />
</div>
{/key}
Step 2: Using the <PageTransition />
component
Create a layout file at src/routes/__layout.svelte
.
<!-- __layout.svelte -->
<!-- 1. Using a `load` function, pass the current URL to the layout component as a prop -->
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export const load = async ({ url }) => ({ props: { url } });
</script>
<script>
import PageTransition from "$lib/components/PageTransition.svelte";
export let url;
</script>
<div>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<!-- 2. Pass the `url` prop to the `PageTransition` component, causing it to re-render when the page changes -->
<PageTransition {url}>
<slot />
</PageTransition>
</div>
The load
function runs before the component is created or updated.
We store the current url in a variable and pass it to the PageTransition
component as the url
prop.
The PageTransition
component will then destroy and recreate itself (transitioning out and back in) only when the user navigates to a new page.
Top comments (13)
Joined to (a) say thanks! and (b) provide some updates that I think are needed. At least for me with the latest SvelteKit builds.
First, during page load, I ASSUME the new page is inserted with opacity of 0 (instead of maybe "display: none") below the old page. This causes anything below the PageTransition component (footer in my case) to jump down and a scrollbar to appear or lengthen depending on the original page content. Pretty jarring. This didn't happen in an earlier build, so maybe it was a change in Svelte/SvelteKit somewhere?? Anyway, the fix was kinda tricky.
I had to wrap the whole thing in a display-grid div with only a single cell and then force the content div into that cell. Bit of a hack, but it fixed the issue.
Second, definitely related to the change from page to url in the load function. The url is a URL object, not a string. At least in recent builds. SO, even when clicking on the same link (e.g., clicking on home while viewing home), the transition effect occurred since the {key} was detecting a changed object even though the underlying values remained unchanged.
ANYWAY, the fix there is easy. On the __layout page (or wherever), change the prop to be url.href. E.g.,:
Now it's a plain string and behaves as expected.
Adding
sveltekit:noscroll
attribute to all<a>
tags fixed the scrolling to top issue for me<a href="path" sveltekit:noscroll>Path</a>
Have you ever tried this approach using a fixed navigation? In all of the demos I've seen, when you click on a link, the site jumps you to the top of the page before transitioning the page out. Do you know why this happens, and how to prevent it?
I'm having the same issue with bulma and fixed navbar. The only way to mitigate is to remove the out transition.
I am seeing this too. Not sure how to fix at the moment.
I'm guessing it's because there's a moment mid-transition where the previous page content is unmounted and the new page content hasn't yet mounted, resulting in a document height that doesn't exceed the window height.
I know Gatsby and NextJS have solutions for persisting scroll position; I wonder if there's something like that out there for SvelteKit?
Thanks for this nice tutorial!
It seems like the variable names have changed, so that "export const load = async ({ page })" has to be changed to "export const load = async ({ url })". Don't forget the props: "key: page.path" to "key: url"
Ah, thanks, I hadn't seen that! I'll update this post to reflect those changes
Thank you for great job. Is it possible to use
import { page } from '$app/stores'
, how do you think? I tried to use$page.path
as a key, but it has a bug in animation between components...What kind of bug? Is it changing to the next route before it finishes the "out" transition of the previous route?
If so... and I'm not sure I'll word this right... but essentially, I think the issue is that the
$page.path
variable is reactive and updates immediately when you navigate to the next route, irrespective of which render instance of the component it started with. With the approach shown in the post, we store a reference to the previous route that's scoped to the particular render instance, soPageTransition.svelte
doesn't see a new key until after the first route has transitioned out (destroyed itself) – then, prior to transitioning back in (recreating itself), itload
s the new key for the new route.Thank you for this, I approached this by using $page store directly but basically new page didn't occur, I just saw transition out. This could explain the behavior
thank you very much for this post, simple and functional
My pleasure! Glad it was helpful.
Perfectly done! And worked the first time!
Thanks for sharing