Search code examples
cssvue.jsvuejs3tailwind-css

Animating width change of an element when a sibling renders conditionally with v-show


I have a Vue.js application where I'm using tailwind for styling. In my template, within a flex parent container, I have two child elements: one that is always rendered, and another that is conditionally rendered using v-show.

The issue is when the conditional element is rendered, it causes the first element's width to adjust automatically to accommodate the space taken by the second element within the flex container. This behavior is expected, but I'd like to add a smooth animation to the width change of the first element (for example, a sliding animation).

This is a simplified structure of my template:

<div>
  <div
    v-for="(group, groupName, index) in groups"
    :key="groupName"
    class="flex flex-row mb-2 relative"
  >
    <Transition
      mode="in-out"
      enter-from-class="-translate-x-[150%] opacity-0"
      leave-to-class="-translate-x-[150%] opacity-0"
      enter-active-class="transition duration-200"
      leave-active-class="transition duration-200"
    >
      <div class="join join-vertical" v-show="buttonsVisibility[groupName]">
        <!-- Conditionally rendered element -->
      </div>
    </Transition>

    <div class="shadow-md rounded-lg collapse">
      <!-- The element I would like to animate when the other element is shown/hidden -->
    </div>
  </div>
</div>

Current behavior:

Current behavior

How can I achieve an animated transition effect for the element in question? Any insights or suggestions would be greatly appreciated!

I've tried to use the Vue's built-in "Transition" component & Tailwind's different transition proprieties for the element with dynamically changing width, but with no result.


Solution

  • This is a viable solution to provide an animation on the given flexbox element.

    <script setup>
    import { ref } from 'vue'
    
    const shown = ref(true)
    </script>
    
    <template>
      <nav class="flex">
        <div class="first" :class="shown && 'open'">first block</div>
        <div class="second" @click="shown = !shown">second block that we do not really care about</div>
      </nav>
    </template>
    
    <style>
    .flex {
      display: flex;
      height: 50px;
    }
    .second {
      background-color: #e9edc9;
      flex: 12 0; /* set to 1 0 if you want to have an even width for the elements */
    }
    
    .first {
      background-color: #caf0f8;
      width: 0; /* this is important for the element to shrink */
      display: hidden; /* this replaces the v-show in a better way */
      transition: all 250ms ease-in-out;
    }
    
    .open {
      background-color: #90e0ef;
      flex: 1 0; /* where the transition happens */
    }
    </style>
    

    Here is a playground in which you can fiddle with things. Nothing here is too complex and can be easily reproduced in Tailwind.

    Some explanation as to what happens above:

    • v-show is adding a display: none; to the element, which is very annoying to make transitions on because of CSS constraints and without hacks, it's better to simply skip that part and use a display: hidden;, which will produce the same visual result but is easy to apply a transition on
    • in my example, I used a @click="shown = !shown" but you can totally go the @mouseenter or @mouseleave way too of course
    • flex: 12 0; is to emulate the small width you want on the side by making the parent 12 times bigger
    • I did not use any translations because it makes more sense to have this kind of width repartition since we're using flexbox, btw here is a very good written reference if you do not know it yet
    • in this situation, there is no real need for a <transition> component, I tried to keep things simple and straight to the point so that you could transfer it to Tailwind easily
    • :class="shown && 'open'", means that we should have a .open on the element if shown is truthy