26

I'm using the following code to animate v-if element by reducing the height to 0px from. The animation works fine. But the problem is I've to specify the initial height of the element is CSS. For one element this is ok, But I want to apply this animation to multiple elements. How can I fix this? So that whatever be the height, animation works fine!

<transition name="fadeHeight" mode="out-in">
<div v-if="something">
<p>something over here where the height is not constant</p>
</div>
</transition>

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

4 Answers 4

45

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/

Sign up to request clarification or add additional context in comments.

3 Comments

This is not a clean animation and should not be the accepter answer, this is just a CSS hack. Setting higher max-height values will delay the animation in one way and reduce the transition time in the other.
Totally agree it's not perfect, but it's not a hack - it's directly answering the question being asked. The answer already points out the potential problems/variation in the effect depending on the size relative to the max-height value used. If you are looking for something different, there are some great breakdowns of the alternatives and pros/cons here: css-tricks.com/using-css-transitions-auto-dimensions
There is no need to use transition component with the max-height approach. As it does not contribute to anything but more code.
10

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!

2 Comments

This doesn't seem to work. When the component is mounted, it is hidden (display: none) so getBoundingClientRect returns 0 height.
Sending you virtual kisses; an elegant solution. And thanks for making it with Composition API and TS! Pain free setup for me; using it in a few places. Main plus is the auto height calc; everything else can be done with standard anim class.
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);                                                 
}

Comments

0

As mentioned - maxheight transition is usually the way to solve this issue. but in some cases you might not be able to use maxheight transitions. For those cases you can use a wrapper container component that will make the transition when needed.

<template>
  <div
    class="fluid-wrapper"
    :class="{ 'in-transition': transitionState }"
    :style="computedDimensions"
    @transitionend="transitionState = 0"
  >
    <slot />
  </div>
</template>
<script>
export default {
  name: 'FluidContainer',
  props: ['trigger'],
  data() {
    return {
      oldRect: {
        height: null,
        width: null,
      },
      newRect: {
        height: null,
        width: null,
      },
      transitionState: 0,
      // 0: no Dimensions, no transition
      // 1: oldRect Dimensions, transition is on
      // 2: newRect Dimensions, transition is on
    };
  },
  computed: {
    computedDimensions() {
      if (!this.transitionState) {
        return null;
      }
      return this.transitionState === 1 ? this.oldRect : this.newRect;
    },
    dimensionsHasChanged() {
      return (
        this.newRect.height !== this.oldRect.height
        || this.newRect.width !== this.oldRect.width
      );
    },
  },
  watch: {
    trigger() {
      const oldStyle = getComputedStyle(this.$el);
      this.oldRect.height = oldStyle.height;
      this.oldRect.width = oldStyle.width;
      this.$nextTick(() => {
        const newStyle = getComputedStyle(this.$el);
        this.newRect.height = newStyle.height;
        this.newRect.width = newStyle.width;
        if (this.dimensionsHasChanged) {
          this.transitionState = 1;
          window.requestAnimationFrame(() => {
            this.transitionState = 2;
          });
        } else {
          this.transitionState = 0;
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.fluid-wrapper {
  /* overflow: hidden; */
  height: fit-content;
  width: fit-content;
  &.in-transition {
    transition: all 0.3s;
  }
}
</style>

Usage:

<FluidContainer :trigger="some-variable">
    <!-- Any Reactive Content -->
</FluidContainer>

‘trigger’ prop - is required for this to work. it can be any piece of state that makes the inner content change. the wrapper will watch the trigger in order to detect when a change in dimensions occurs and make the transition.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.