Page transitions in layout don't works well using the `page` store
Describe the bug
We all like Svelte transitions, and some of us are using them for page transitions. A good repo. example for page transitions is this one, by basically doing something like this code in the main layout:
{#key pathname}
<div in:fly={{ x: -5, duration: 500, delay: 500 }} out:fly={{ x: 5, duration: 500 }}>
<slot />
</div>
{/key}
The repo. I mentioned, is passing the pathname in the load function of the layout, so in their code, pathname equals to data.pathname. The disatvantage of this method, is that the main layout load function will be invalidated every time the page is changing, which is probably very wasteful.
The obvious solution is to use the page store instead, and substitute in pathname the value of $page.url.pathname instead.
However, the transition fails, and it looks like the page store is being updated too late, causing the transition to take place after the slot has already been changed, resulting with the effect that the new page is also rendered on the outro transition:
https://user-images.githubusercontent.com/1467072/204525557-1fa4658f-3129-4c8f-b665-355c1f420f8b.mp4
Upgrading the svelte and the @sveltejs/kit packages results with the same effect.
Reproduction
- Clone the repo. from before,
npm installandnpm run dev. You'll see everything works well. - Now in https://github.com/evanwinter/sveltekit-page-transitions/blob/main/src/routes/%2Blayout.svelte#L14, change the row from
<PageTransition pathname={data.pathname}>to<PageTransition pathname={$page.url.pathname}>, and of course import thepagestore byimport { page } from '$app/stores';. You'll see now the same results as in the video above.
Logs
No response
System Info
System:
OS: Windows 10 10.0.19044
CPU: (4) x64 Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
Memory: 4.38 GB / 15.49 GB
Binaries:
Node: 18.3.0 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.18 - ~\AppData\Roaming\npm\yarn.CMD
npm: 8.14.0 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.19041.1266.0), Chromium (107.0.1418.62)
Internet Explorer: 11.0.19041.1566
npmPackages:
@sveltejs/adapter-netlify: ^1.0.0-next.72 => 1.0.0-next.72
@sveltejs/kit: ^1.0.0-next.436 => 1.0.0-next.436
svelte: ^3.49.0 => 3.49.0
vite: ^3.0.9 => 3.0.9
Severity
serious, but I can work around it
Additional Information
No response
I don't think there's anything we can do here. The page store only updating after the navigations completes is the correct behavior. To do what you want it's probably better to use the $navigating store to map all changes from null to <something> to a key that changes every time that happens.
This sounds like something for an examples or recipes section.
I don't think there's anything we can do here. The
pagestore only updating after the navigations completes is the correct behavior. To do what you want it's probably better to use the$navigatingstore to map all changes fromnullto<something>to a key that changes every time that happens.This sounds like something for an examples or recipes section.
Thanks for the workaround! If it helps, the key I used was ($navigating?.to ?? $page.url).pathname, but it have the problem that it can animate a page entrance even when the page wasn't fully loaded, resulting the same issue but the oppoisite - now on heavy load, the old page will entrance as well, and suddenly, when the loading finish, the new page will be replaced immediately.
For fixing this potential behavior, you need to do both use key and if blocks with navigation:
{#key $navigating}
{#if !$navigating}
<div in:fly={{ x: -5, duration: 500, delay: 500 }} out:fly={{ x: 5, duration: 500 }}>
<slot />
</div>
{/if}
{/key}
This will make the previous page to always be transitioned out, but the new page will not be transitioned in before the navigation is complete.
Side effect of the solution: if the loading takes X milliseconds to complete, the transition in in this code will be only after 500 + X milliseconds because of the delay, when it should have been max(500, X) instead.
A better solution will be to use the following code instead (where outroFinished is a variable initialized to true):
{#if !$navigating && outroFinished}
<div transition:fly={{ x: 5, duration: 500 }} on:outrostart={() => { outroFinished = false; }} on:outroend={() => { outroFinished = true; }} >
<slot />
</div>
{/if}
@Tal500 @dummdidumm
My solution for this was to simply use the afterNavigate() lifecycle hook:
// +layout.svelte
<script>
import { afterNavigate } from '$app/navigation'
let show_special_links = false
afterNavigate(async (nav) => {
const { from, to } = nav;
show_special_links = to.url.href.includes('/special-address');
});
</script>
<Nav>
{#if show_special_links}
<SpecialNavLinks />
{/if}
</Nav>
@Tal500 @dummdidumm My solution for this was to simply use the
afterNavigate()lifecycle hook:// +layout.svelte <script> import { afterNavigate } from '$app/navigation' let show_special_links = false afterNavigate(async (nav) => { const { from, to } = nav; show_special_links = to.url.href.includes('/special-address'); }); </script> <Nav> {#if show_special_links} <SpecialNavLinks /> {/if} </Nav>
Notice that this solution may suffer from the side effect I mentioned in my previous comment.
Notice that this solution may suffer from the side effect I mentioned in my previous comment.
Agreed, but for most use-cases, I think it's going to fit the intended effect with more consistent/predictable behavior for your average user.
<script>
import { navigating } from "$app/stores";
</script>
{#key $navigating}
{#if !$navigating}
<main
transition:fly={{
x: window.innerWidth,
duration: 1000,
easing: elasticInOut,
}}
>
<slot />
</main>
{/if}
{/key}
thanks @Tal500, that worked very fine!