Demo: https://amazing-kirch-8cb3f5.netlify.app/
I have tried before and failed to make my page transtitions implemented in a way that we don't have to import the pageTransition component in each and every route.
I have finally created a pageTransition component that can just be dropped in the $layout.svelte component (_layout.svelte in Sapper) and then be used by all existing or future routes.
I am going to show you here the steps and the thought process for setting up the component and the changes along the way which was the natural steps that I evolved this component and hopefully they can help someone in understanding layout/route based Svelte reactivity better.
There is also a github repo that I have committed all different phases if you need to know all the details. The tutorial is mostly focuses on inexperienced developers. For more experienced developers you can just skip to the last sections or go straight to the repo.
I have chosen SvelteKit for this demonstration just to try it out and mostly because it seems to be the future of Svelte. If you are using Sapper the steps should be almost identical.
Setup Svelte@next
Inside an empty project directory run
npm init svelte@next
pnpm install
pnpm run dev
NOTE: Feel free to use
npm
where I usepnpm
. The two have exactly the same syntax.
After that you can browse to localhost:3000
and be presented with the demo route.
Setup a 2nd route a Simple Navigation component and a $layout component
Setup minimum routes and components to be able to navigate from one page to another.
<!-- src/components/Nav.svelte -->
<div>
<a href="/">Home</a>
<a href="/about">About</a>
</div>
<!-- src/routes/about.svelte -->
<main>
<h1>About Page</h1>
<p>This is the about page</p>
<p>More paragraphs of the about page</p>
</main>
<!-- src/routes/$layout.svelte -->
<script>
import Nav from '../components/Nav';
</script>
<Nav/>
<slot/>
We should have now the two routes and be able to navigate between Home page and About page.
The beauty of SvelteKit (the magic of snowpack) is that you should be able to see changes in your browser immediately after saving the files.
Consistent styling of pages by creating global.css
Some stylistic changes are due to make the styles consistent across all pages.
We take all styles from index.svelte
and add them to a new file static/global.css
/* static/global.css */
:root {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}
p {
max-width: 14rem;
margin: 2rem auto;
line-height: 1.35;
}
@media (min-width: 480px) {
h1 {
max-width: none;
}
p {
max-width: none;
}
}
Link css file from app.html
by adding following line before %svelte.head%
<!-- add to src/app.html -->
<link rel="stylesheet" href="global.css">
Simple PageTransitions component
Here is a simple PageTranstitions svelte component using svelte/transition
package.
<!-- src/component/PageTransitions.svelte -->
<script>
import { fly } from 'svelte/transition';
</script>
<div
in:fly="{{ y: -50, duration: 250, delay: 300 }}"
out:fly="{{ y: -50, duration: 250 }}"
>
<slot/>
</div>
It makes the page fly in and out from top of the viewport. We delay a bit on the in transition to avoid the in and out transitions from working at the same time.
Perhaps if anyone know how to add
crossfade
to it please do let me know in the comments.
If you add it as a wrapper on any route it does work as expected and it is the way I have seen all tutorials use a page transition component. There might also be use cases where we only need it on certain routes and than this would be the way to use it.
<!-- src/routes/about.svelte -->
<script>
import Counter from '$components/Counter.svelte';
import PageTransitions from '../components/PageTransitions.svelte';
</script>
<PageTransitions>
<main>
<h1>About Page</h1>
<p>This is the about page</p>
<p>More paragraphs of the about page</p>
</main>
</PageTransitions>
More versatile PageTransitions component
For most use cases though I believe setting PageTransitions in the $layout.svelte
component and than having all routes use it is the way to go as it we set it and forget it.
First lets remove the component from all routes and bring them to their initial state (setup setps a little above).
If we just put the PageTransitions component in $layout.svelte
component without any modifications though we will not be able to see any transitions.
<!-- src/routes/$layout.svelte -->
<script>
import Nav from '../components/Nav';
import PageTransitions from '../components/PageTransitions';
</script>
<Nav/>
<PageTransitions>
<slot/>
</PageTransitions>
NOTE: I noticed that SvelteKit is not loading all changes automatically to the browser. Perhaps this is something that will be fixed in later versions so lets not focus on it just keep in mind to refresh the browser when you can't see any changes.
The problem is that the component is not reactive as is. We should make it reactive to the change of the route.
Reactive Nav component
Lets first make the Nav component reactive by underlying the current route link.
<!-- src/components/Nav.svelte -->
<script>
export let segment;
</script>
<style>
a {
text-decoration: none;
}
.current {
text-decoration: underline;
}
</style>
<div>
<a href="/" class='{segment === "/" ? "current" : ""}'>Home</a>
<a href="/about" class='{segment === "/about" ? "current" : ""}'>About</a>
</div>
Pass the $page.path
as segment
variable to $layout.svelte
to make the Nav reactive.
<!-- src/routes/$layout.svelte -->
<script>
import Nav from '../components/Nav';
import { page } from '@sveltejs/kit/assets/runtime/app/stores.js'
</script>
<Nav segment={$page.path}/>
<slot/>
Now if you change the Nav links the current route link should be underlined.
Reactive PageTransition component
We now need to make the component reactive by creating a refresh
prop and using key
directive which means that when the key changes, svelte removes the component and adds a new one, therefore triggering the transition.
<!-- src/component/PageTransitions.svelte -->
<script>
import { fly } from 'svelte/transition';
export let refresh = '';
</script>
{#key refresh}
<div
in:fly="{{ y: -50, duration: 250, delay: 300 }}"
out:fly="{{ y: -50, duration: 250 }}"
>
<slot/>
</div>
{/key}
Also lets pass the $page.path
variable to PageTransition component similar to how we did it with the Nav component.
<!-- src/routes/$layout.svelte -->
<script>
import Nav from '../components/Nav';
import PageTransitions from '../components/PageTransitions';
import { page } from '@sveltejs/kit/assets/runtime/app/stores.js'
</script>
<Nav segment={$page.path}/>
<PageTransitions refresh={$page.path}>
<slot/>
</PageTransitions>
You should now have your smooth page transtitions without needing to add the component to every route.
Improving the page transitions
When the pages are longer in height the transition are not actually that smooth because it might cause the scrollbars to flicker creating an undesired moving effect on the contents of the page.
For understanding the problem lets force each page to be a little bigger in height
/* add in static/global.css */
main {
min-height: 600px;
}
Depending on your viewport size you might need to adjust the height so that there is no scrollbars on when not transitioning (idle state).
If you refresh your browser and navigate from page to page you should see the flickering effect.
To mitigate this problem lets add the following css to your global.css
.
/* add in static/global.css */
body {
overflow-y: scroll;
}
And now the scrollbars will be visible at all times but might not always have the scroll handle to drap up/down. This will prevent them from appearing and disappearing and eliminates the problem.
Another alternative would be to use
/* add in static/global.css */
html {
margin-left: calc(100vw - 100%); }
}
Read more about the flickering scrollbar and the above solutions on css-tricks
Conclusion
Making a component reactive on the layout/routing level is easy enough as seen above. This technique can be used with any component we include in layout
or any component that we want to be reactive when the route changes.
Please post your thoughts on the comments below. I would be happy to improve on the above code if something is not considered right or best practice and I do welcome corrections.
If you liked this tutorial or found it useful please share or like or give me a star on github. Any of the above gives me an incentive to dig deeper and write more useful tutorials.
Top comments (28)
I've created a REPL which shows how to use a crossfade between pages. svelte.dev/repl/0ad58a0d830f4001b9...
Hint: Use the same instance of the crossfade by creating the send and receive in a separate javascript file.
This was exactly the demo I needed, and it easily works to move around multiple UI elements at the same time. So cool!
svelte.dev/repl/b2a5fbb5ec86486bb7...
I'm not sure in which version it happened... but the
segment
prop no longer appears to be valid. I've yet to find what the appropriate replacement is.@skwasha I have updated the repo and replaced
segment
with$page.path
and updated also the tutorial to reflect this change. Let me know if this works for you.Hi @giorgosk, Great tutorial although my page fades are not ideal.
P.S:
$page.path
seems to have changed to$page.route.id
?Hey, that's great! Thanks for the update. Where did you happen to find the location for the page stores? I tried searching and checking old messages on the Discord but never saw any mention of it. Do you know if that's going to be where these "built in" stores will be located moving forward with Svelte Kit?
thanks again.
@skwasha I started digging in the sveltekit
node_modules/sveltejs/kit/assets/runtime/app/stores.js
code looking for$page.path
mentioned in the github.com/sveltejs/sapper/issues/824 and found it. Perhaps the importimport { page } from '@sveltejs/kit/assets/runtime/app/stores.js';
will get more elegant when sveltekit gets released (or I might be missing the correct way to do it) but this will be the way to use it again according to the issue/824.I searched the discord and found the right way to do it:
import { page } from '$app/stores';
Thanks for this article, by the way, it's helping me get off the ground with svelte/kit.
Another idea for a change, you can set the
class="current"
like this, it's a bit shorter:<a href="/" class:current={segment==="/"}>Home</a>
<a href="/about" class:current={segment==="/about"}>About</a>
Yes there is some talks about removing segment from sapper but nothing has been committed yet. In here github.com/sveltejs/sapper/issues/824 you can find the discussion and some alternatives (using $page store)
Yes. I saw that discussion as well. I guess my point was that the above code is no longer functional (at least the bits that rely on
segment
). Somewhere along the way that proposed change seems to have made it into the current sveletkit branch.Does anyone know where the
page
store is in the Sveltekit world? Previously, it was in@sapper/app
.import { page } from '$app/stores';
Great. But currently in sveltekit, you can just use $page.url.pathname to get the current page's path for the key in the PageTransition component. So there should no be any need to export the
refrech
prop.it's now
$page.route.id
Good read!
I think it would have been much better with short video clips of how the transitions are working with the changes.
Sorry I don't understand why would you need a video when there is a live site demo amazing-kirch-8cb3f5.netlify.app/ ?
No, I meant the difference in the transitions before and after making changes for improvement.
Ok I see perhaps I can do it when I get a chance. In the meantime if you need to see the improvements you can toggle the last css changes on and off on your locally installed repo.
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?
Late reply, but I don't think it has anything to do with a fixed nav. I think it's just invoking a transition from a scroll position other than the top of the page. At least I think I eliminated all of the variables and it was still happening. My guess is that the transition code in Svelte simply wasn't intended for content the size of a whole page and does something to trigger the browser to jump. Couldn't find it in their code, but I didn't look too hard. Anyway, the only solution I found was simply to remove the out: transition altogether. Just do an in:fade. Looks okay. Better than the "cut" default.
@giorgosk did you manage to figure out how to handle routing from code ? in sapper for example you have the "goto" method for routing. i couldn't find this feature in sveltekit
Sorry not done any further research on it
If I try to use PageTransition in the layout instead of the component I get the following error, do you know why?
Error: failed to load module for ssr: /Users/userName/Documents/GitRepos/project/src/lib/components/PageTransitions.svelt
Thanks a lot for your great tutorial. In my project I am using Svelte + GSAP and I would also like to use page transitions. I implemented your code, but GSAP animations don't work then. Would you have any advice on how to fix it?
Here is a demo:
Without Page Transitions:
stackblitz.com/edit/sveltejs-gsap-...
With Page Transitions:
stackblitz.com/edit/sveltejs-gsap-...
I would appreciate any help. Thank you!
dang, this is what i need,,
cool post,, thanks for the idea,,
Great article Giorgos!! Congrats.
Glad you liked it Pavlos, comments are always appreciated !!!