tailwind-csscss-transitionssveltesveltekitsvelte-transition

svelte transition:slide is jumpy when component switch with different height happen


i have a two forms, one for sendOTP and the other one for verifyOTP and the verify otp one is taller than the send otp. the height transition for my container is jumpy on getting back from second form to first one.

i've used transition:slide to smoothly change the height, but when i'm going back from second form to first one the slide goes to the height of verify otp form(the taller one) and jump to the height of first form. i've used fly transition as well but same issue persisted, at first i wanted a transition from right to left between forms with fly transition x: -300, x: 300 but not working on height.

this is my code:

<div class="flex overflow-hidden"
     transition:slide={{ duration: 500, easing: circInOut }}>
    {#if !$showOTPForm}
        <div
            class="min-w-full"
            out:slide={{ duration: 500, easing: circInOut }}
            in:slide={{ duration: 500, delay: 500, easing: circInOut }}
        >
            <SendOtpForm on:success={handleLoginSuccess} />
        </div>
    {:else if $showOTPForm}
        <div
            class="min-w-full"
            in:slide={{ duration: 500, delay: 500, easing: circInOut }}
            out:slide={{ duration: 500, easing: circInOut }}
        >
            <VerifyOtpForm {phoneNumber} on:back={handleBackToSendOtp} />
        </div>
    {/if}
</div>

Solution

  • Try the following:

    To your Script tag add:

    <script>
        // bind the height of your two forms
        let sendOtpHeight
        let verifyOtpHeight
        
        // Reactively switch the height of the container using the same
        // condition as the {#if} block
        $: containerHeight = !$showOtpForm ? sendOtpHeight : verifyOtpHeight
    </scritp>
    

    In the HTML part, bind the client heights of the wrappers and inline the height of the container concatenating the containerHeight value. Add a transition to the height and it should grow and shrink smoothly.

    <div class="flex overflow-hidden"
         style="height: {containerHeight}px; transition: height 0.6s ease-in-out"
         transition:slide={{ duration: 500, easing: circInOut }}>
        {#if !$showOTPForm}
            <div
                class="min-w-full"
                out:slide={{ duration: 500, easing: circInOut }}
                in:slide={{ duration: 500, delay: 500, easing: circInOut }}
                bind:clientHeight={sendOtpHeight}
            >
                <SendOtpForm on:success={handleLoginSuccess} />
            </div>
        {:else if $showOTPForm}
            <div
                class="min-w-full"
                in:slide={{ duration: 500, delay: 500, easing: circInOut }}
                out:slide={{ duration: 500, easing: circInOut }}
                bind:clientHeight={verifyOtpHeight}
            >
                <VerifyOtpForm {phoneNumber} on:back={handleBackToSendOtp} />
            </div>
        {/if}
    </div>
    

    The issue may persist because during the switch both wrappers are children of the container. To fix that, add position:relative to the container and make both wrappers position:absolute; top: 0; left: 0. Something like this:

    <div class="flex overflow-hidden"
         style=" height: {containerHeight}px; 
                 transition: height 0.6s ease-in-out;
                 position:relative;
               "
         transition:slide={{ duration: 500, easing: circInOut }}>
        {#if !$showOTPForm}
            <div
                class="min-w-full form-wrapper"
                out:slide={{ duration: 500, easing: circInOut }}
                in:slide={{ duration: 500, delay: 500, easing: circInOut }}
                bind:clientHeight={sendOtpHeight}
            >
                <SendOtpForm on:success={handleLoginSuccess} />
            </div>
        {:else if $showOTPForm}
            <div
                class="min-w-full form-wrapper"
                in:slide={{ duration: 500, delay: 500, easing: circInOut }}
                out:slide={{ duration: 500, easing: circInOut }}
                bind:clientHeight={verifyOtpHeight}
            >
                <VerifyOtpForm {phoneNumber} on:back={handleBackToSendOtp} />
            </div>
        {/if}
    </div>
    
    <style>
    .form-wrapper {
        position: absolute;
        top: 0px;
        left: 0px;
    }
    </style>