Animate Height on v-if in Vuejs using Transition

I had some troubles with this one, and many of the answers out there were way to complex IMO. After a while, this is what i came up with to make a smooth height transition for "height: auto" content:

<template>
 <transition name="expand">
   <div v-show="isExpanded" ref="content">
     <slot />
   </div>
 </transition>
</template>

<script setup lang="ts">
import { onMounted, ref } from '@vue'

defineProps<{isExpanded: boolean}>()
const content = ref()
let height = ref()

onMounted(() => {
  height.value = `${content.value.getBoundingClientRect().height}px`
})
</script>

<style scoped lang="less">
.expand-leave-active,
.expand-enter-active {
  transition: all 350ms ease;
  overflow: hidden;
}

.expand-enter-to,
.expand-leave-from {
  height: v-bind(height);
}

.expand-enter-from,
.expand-leave-to {
  opacity: 0;
  height: 0;
}
</style>

Hope this helps someone!


It doesn't look like you've posted all the code, but hopefully I understand the goal.

Try moving the transition to the max-height property:

.fadeHeight-enter-active,
.fadeHeight-leave-active {
  transition: all 0.2s;
  max-height: 230px;
}
.fadeHeight-enter,
.fadeHeight-leave-to
{
  opacity: 0;
  max-height: 0px;
}

as long as you set a max height to be larger than the tallest element, it should accomplish what you need. Note that you may also want to use overflow:hidden as well. If you have dramatic variation of the actual height of the elements, this solution may not be the best, as it will make the animation duration/delay appear very different.

https://jsfiddle.net/7ap15qq0/4/


@ryantdecker has the most common answer available. I prefer doing less code though and do class binding instead:

<template>
 <!-- isShowing either data or computed... -->
 <div class="foo" :class="{ showing: isShowing, hidden: !isShowing }">
  <p>
   something here where the height is not constant
  </p>
 </div>
</template>
...
<style>
.foo {
 height: auto;
 transition: max-height 0.5s;
 &.showing {
  max-height: 200px; /* MUST BE GREATER THAN height:auto */
 }
 &.hidden {
  max-height: 0px;
 }
}
</style>

A few customizations that can be done for even more control are:

  1. Set :style="{'max-height': computedHeight}"
  2. Use ease-in and ease-out with two different transitions within the .showing and .hidden classes respectively.
  3. Use a cubic bezier transition speed for really long contents that collapse/expand

The first customization above can be used when you are using distinct items, like pictures, flex rows where the height can be seen via devtools and the height calculated. E.g.:

computed: {
 /**
  * @return {string} max height of the container in pixels if shown else zero
  */
 calcedHeight()
 {
   const elHeight = 80;
   const maxHeight = this.isShowing ? elHeight * this.elementCount : 0
   const maxHeightPx = maxHeight + 'px'
   return {
    'max-height': maxHeightPx
   }
 }
}

This could easily be made into a component with isShowing, elHeight, and elCount props at this point.

Cubic Bezier

I am giving this it's own section because it might be all that is needed in regards to crazy long elements (think 5000px max-heights):

&.showing {                                                                                          
   transition: all 0.6s cubic-bezier(1, 0.01, 1, 0.01);                                                 
}                                                                                                       
&.hidden {                                                                                           
   transition: all 0.6s cubic-bezier(0.01, 1, 0.01, 1);                                                 
}