Svelte Transitions: How to Make Incoming "in" Dynamic Svelte Component transition Wait for Outgoing "out" Svelte Component to Finish
Asked Answered
B

4

7

Essentially I'm working on a slideshow project where each "slide" is loaded dynamically using <svelte:component this={currentSelection.component} />. Each slide requires custom in and out transitions on a component-by-component basis. I'd like for the next slide to "wait" while the current slide finishes transitioning, however, as stated in the svelte documentation:

Unlike with transition:, transitions applied with in: and out: are not bidirectional — an in transition will continue to 'play' alongside the out transition, rather than reversing, if the block is outroed while the transition is in progress. If an out transition is aborted, transitions will restart from scratch.

Is there a sensible way to make the next slide "wait" until the current slide is finished with its outro transition?

Toy Example at REPL

Toy code posted for posterity:

//App.svelte
<script>
    import RedThing from './RedThing.svelte';
    import GreenThing from './GreenThing.svelte';
    import BlueThing from './BlueThing.svelte';

    const slides = [
            RedThing,
            BlueThing,
            GreenThing
    ];
    let currentComponent = 0;
    const prev = () => currentComponent--;
    const next = () => currentComponent++;

</script>

<button on:click={prev}>Prev</button><button on:click={next}>Next</button>
<div>
    <svelte:component this={slides[currentComponent]}/>
</div>

//RedThing.svelte
<script>
    import { fly, slide } from 'svelte/transition';
</script>

<style>
    div { color: red; }
</style>

<div in:fly out:slide>red thing</div>

//GreenThing.svelte
<script>
    import { fade, slide } from 'svelte/transition';
</script>

<style>
    div { color: green; }
</style>

<div in:slide out:fade>green thing</div>
//BlueThing.svelte
<script>
    import { scale, fade } from 'svelte/transition';


</script>

<style>
    div { color: blue; }
</style>

<div in:scale out:fade>blue thing</div>

Edit: I should add a complication – I am driving component traversal through sapper anchor tags, which are taking care of component creation / destruction. In other words:

<a href={prev} id="previous-button">Previous</a>
<a href={next} id="next-button">Next</a>

<div>
    <svelte:component this={slides[currentComponent]}/>
</div>

I'm not sure if that makes a difference?

Boysenberry answered 26/1, 2020 at 14:50 Comment(1)
It’s been quite a while, but my answer might still be of use to you :)Salzhauer
E
7

I know this thread is a few month old by here is a simple solution. I had also problems with this. The secret? The delay parameter:

The REPL


// RedThing.svelte
<script>
    import { fly, slide } from 'svelte/transition';
</script>

<style>
    div { color: red; }
</style>

<div in:fly="{{delay: 300, duration: 300}}" out:slide="{{duration: 300}}">red thing</div>

// GreenThing.svelte
<script>
    import { fade, slide } from 'svelte/transition';
</script>

<style>
    div { color: green; }
</style>

<div in:slide="{{delay: 300, duration: 300}}" out:fade="{{duration: 300}}">green thing</div>
// BlueThing.svelte
<script>
    import { scale, fade } from 'svelte/transition';
</script>

<style>
    div { background-color: blue; }
</style>

<div in:scale="{{delay: 300, duration: 300}}" out:fade="{{duration: 300}}">blue thing</div>

Eta answered 4/3, 2020 at 14:26 Comment(1)
I've been running into the same UI patter issue where I have a bunch if div blocks each in it's svelte if clause e.g. {#if currentlyOpenTab == 'profile_settings' } content... {:else if currentlyOpenTag == 'team' } team content etc. {/if}. At first I tried with absolute positioning on the div blocks which brings just to much side effects on the rest of my UI layout so I also decided to go with non-sveltish-workaround of using a delay such as <div in:fly|local="{{ x: -1000, duration: 500, delay: 400 }}" out:fly="{{ x: 1000, duration: 400 }}">. Isn't there a built-in for this pattern?Anagnos
B
1

I've found a semi-workable solution to my issue by adding position: absolute; to each dynamic component's container. This works because transitions append the incoming component to the dom as a sibling to the old one before destroying it. By making the position absolute, the outgoing and incoming components inhabit the same position. A bit of fade tweaking makes it look ok. This is not an ideal solution, but it may suffice.

Example:

//RedThing.svelte
<script>
    import { fly, slide } from 'svelte/transition';
</script>

<style>
    div { color: red; }
</style>
<div style="position:absolute;" transition:fade={{duration: tweaky}}>
    <div in:fly out:slide >red thing</div>
</div>
//GreenThing.svelte
<script>
    import { fade, slide } from 'svelte/transition';
</script>

<style>
    div { color: green; }
</style>
<div style="position:absolute;" transition:fade={{duration: tweaky}}>
    <div in:slide out:fade >green thing</div>
</div>

Inspired / stolen by this solution to create sapper crossfades between pages: https://dev.to/buhrmi/svelte-component-transitions-5ie

Boysenberry answered 28/1, 2020 at 16:35 Comment(0)
P
0

This assumes consistent duration for all transitions. Added delay for intro that matches outro duration in each slide component. If you want different durations for each slide transition, you might need to track and match them (may be in a store).

<script>
    import { scale, fade } from 'svelte/transition';
</script>

<style>
    div { background-color: blue; }
</style>

<div in:scale={{duration: 500, delay: 500}} 
     out:fade={{duration: 500}}
     >
     blue thing
     </div>
Parade answered 21/1, 2022 at 18:48 Comment(0)
P
0

Quite a bit late to the party, but I feel it is still worth sharing what I came up with :)

To begin with, I made use of the delay property (suggested by MaddEye) to make sure that the animation of the outgoing element would not overlap the incoming one’s. That was a good start, but I was experiencing a content reflow during that instant in which these two elements coexist. That’s when I felt that fish’s suggestion could come in handy, but I really wanted to avoid setting a position: absolute to each element because it could easily cause different kinds of issues.

That’s when I remembered there is another way to achieve the same effect without the drawbacks of position: absolute. It consists in setting display: grid on the parent element, and then assigning grid-column and grid-row with specific values to the child elements so that they end up sitting in the same position, in the same grid area. Plus you can even align them as you wish thanks to the grid layout :)

I’ll leave you a demo REPL to better illustrate my explanation ;)

Peart answered 25/2, 2023 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.