Search code examples
animationsveltesveltekit

Svelte - Transitions in layout not delaying {#key}ed <slot> update


I'm trying to add a transition between pages in a SvelteKit application. When navigating, the current page should fade out, and the new page should then fade in in its place. To accomplish this, in +layout.svelte, I wrapped the <slot> in a div with the in and out transitions set. I wrapped this all in {#key $page.url.pathname} so that the animations are triggered when navigating from page to page. However, my current code produces this effect:

An animated gif of the problem

When navigating, the content of the page updates before fading out. In other words, the destination page immediately appears, then fades out, then fades back in. At the same time, though, content in +layout.svelte (.title, at the top of the page) behaves correctly; just the content within the <slot> is bugged.

Here is the code:

+layout.svelte

<script lang="ts">
    import '$lib/style.css';

    import { page } from '$app/stores';
    import { fade } from 'svelte/transition';
</script>

<div class="page">
    <div class="bar">
        Sidebar
        <a href="/">Page 1</a>
        <a href="/page2">Page 2</a>
    </div>

    <div class="content-wrapper">
        {#key $page.url.pathname}
            <div class="content" in:fade={{ delay: 1000, duration: 1000 }} out:fade={{ duration: 1000 }}>
                <div class="title">
                    {$page.url.pathname}
                </div>

                <slot />
            </div>
        {/key}
    </div>
</div>

<style global>
    .page {
        display: flex;
        flex-direction: row;

        box-sizing: border-box;
        width: 100vw;
        min-height: 100vh;
    }

    .bar {
        display: flex;
        flex-direction: column;

        width: 300px;

        color: white;
        background: black;
    }

    a {
        color: white;
    }

    .content-wrapper {
        flex-grow: 1;

        width: 100%;
        min-height: 100vh;
    }

    .content {
        width: 100%;
        height: 100%;
    }

    .title {
        font-size: 2em;
    }
</style>

+page.svelte

<div class="page">
    <div class="title">Page 1</div>
</div>

<style>
    .page {
        width: 100%;
        height: 100%;

        background: blue;
    }
</style>

page2/+page.svelte

<div class="page">
    <div class="title">Page 2</div>
</div>

<style>
    .page {
        width: 100%;
        height: 100%;

        background: red;
    }
</style>

Is there a way to get the <slot> content to wait for the out animation to finish before updating?


Solution

  • Instead of using the $page store, the slot content seems to be updated correctly when moving the logic to the layout.js load function.

    REPL

    +layout.js
    export const load = ({ url }) => {
      const { pathname } = url
    
      return {
        pathname
      }
    }
    
    +layout.svelte
    <script>
        import { fly } from 'svelte/transition';
    
        export let data;
    
    </script>
    
    {#key data.pathname}
        <div
            class="transition-wrapper"
            in:fly={{ x: 100, duration: inDuration, delay: outDuration }}
            out:fly={{ x: -100, duration: outDuration }}
        >
            <slot />
        </div>
    {/key}