Search code examples
sveltesvelte-transition

Why is my svelte crossfade moving my elements instead of crossfading in place?


I'm trying to fade one element out and another in based on a boolean value. Instead of having a crossfade where the two elements look like they're in the same place, they shift vertically. I'd like to get them to stay where they would sit naturally!

Stackblitz

<script lang="ts">
  import { crossfade } from "svelte/transition";

  const [send, receive] = crossfade({
    duration: 1500,
  });

  let foo = true;

  function handleClick() {
        foo=!foo;
    }
</script>

<h1>My example</h1>
<button on:click={handleClick}>Click me {foo}</button>
<div>
  {#if foo}
    <div in:send={{ key: "a" }} out:receive={{ key: "a" }}>
      one
    </div>
  {:else}
    <div in:send={{ key: "a" }} out:receive={{ key: "a" }}>
      two
    </div>
  {/if}
</div>

Solution

  • crossfade is precisely for transitioning elements that are in different locations. For an in-place transition you can just use fade.

    Though the main reason why there is movement in the first place is layout. During the transition both elements exist and push each other around. This can be fixed in various ways, e.g. by listening to transition events and setting something like position: absolute on the disappearing one or making a container put both elements in the same place.

    E.g.

    <script lang="ts">
      import { fade } from "svelte/transition";
      let show = true;
    </script>
    
    <button on:click={() => show = !show}>
        Fade [{show}]
    </button>
    
    <div class="container">
      {#if show}
        <div transition:fade={{ duration: 1500 }}>one</div>
      {:else}
        <div transition:fade={{ duration: 1500 }}>two</div>
      {/if}
    </div>
    
    <style>
     .container { display: grid; }
     .container > * { grid-area: 1 / 1; }
    </style>
    

    REPL