csscss-animationssveltesveltekitsvelte-transition

Image slider - While rendering img using {#each} by clicking buttons that change visible image, transitions and animations don't work


It's my first time asking question on stack overflow and from what I'm feeling I'm missing something very small or don't understand the logic correctly. I show images by {#each} block and slice by adding and subtracting from variables: selected, prevSelected. From what I understood in svelte tutorial on transitions it should work (https://svelte.dev/tutorial/local-transitions), for some reason images just show up in a blink without transitions. I tried remaking the logic in the function, timeouts and restarting and none seemed working.

Please explain to me what I'am missing. Sorry for little knowledge, picked up svelte as my first frontend framework to learn yesterday. I'm using the latest sveltekit version.

Edit: I'm also using latest tailwind css.

<script>
    import { slide,fade } from 'svelte/transition';
    let images = [
        {
            Index: 0,
            src: 'src/images/img2.jpeg',
            alt: 'znaki ewakuacyjne',
            class: 'show'
        },
        {
            Index: 1,
            src: 'src/images/img3.jpeg',
            alt: 'buty',
            class: ''
        },
        {
            Index: 2,
            src: 'src/images/img4.jpeg',
            alt: 'znaki informacyjne',
            class: ''
        }
    ];
    let selected = 1;
    let prevSelected = 0;
    let shown=true;
    function handleClick() {
        if (selected == 3) {
            return;
        }
        prevSelected = selected;
        selected += 1;
    }
    function handleBack() {
        if (selected == 1) {
            return;
        }
        prevSelected -= 1;
        selected -= 1;
    }
</script>

<h3 class="text-center font-semibold text-xl m-3">Galeria</h3>
<div class="image-gallery flex flex-col justify-center items-center">
    {#if shown}
    {#each images.slice(prevSelected, selected) as img}
        <img
            transition:fade
            src={img.src}
            alt={img.alt}
            class="h-80 w-50 rounded-lg"
        />
    {/each}
    {/if}
    <div class="flex flex-row gap-12">
        <button
            on:click={handleBack}
            type="button"
            class="text-white bg-red-700 hover:bg-red-800 focus:outline-none focus:ring-4 focus:ring-red-300 font-medium rounded-full text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900 m-3"
            >⬅️</button
        >
        <button
            on:click={handleClick}
            type="button"
            class="text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 m-3"
            >➡️</button
        >
    </div>
</div>

<style lang="scss">
</style>

Solution

  • You need to key the {#each} (see docs), e.g.

    {#each images.slice(prevSelected, selected) as img (img)}
    

    Otherwise the same component is reused and only its properties get changed.

    Note that you may have to adjust the layout to make both images take the same place, otherwise the image that fades out will push away the one that fades in.

    By the way, if you only ever show one image, you do not need an each at all, only {#key} and the index.

        {#key selected}
            <img
                transition:fade
                src={images[selected - 1].src}
                alt={images[selected - 1].alt}
                class="h-80 w-50 rounded-lg"
            />
        {/key}
    

    Could be simplified even further via a reactive statement that always resolves to images[selected - 1]. prevSelected is redundant and I would change the logic of selected to be 0-based instead of 1-based.