3

In my Vue app I have a simple toggle which renders a list when active. The list should not appear/disappear instantly. I want to have a smooth slide down transition on render and a smooth slide up transition on hide.

The following code shows what I have so far, does someone know how to make it work?

new Vue({
  el: '#app',
  data: () => ({
    isOpen: true,
  }),
});
.expand-enter-active,
.expand-leave-active {
  overflow: hidden;
  transition: height .5s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <button @click="isOpen = !isOpen">
    is open: {{ isOpen }}
  </button>
  <transition name="expand">
    <div v-if="isOpen">
      <div>1</div>
      <div>2</div>
      <div>3</div>
    </div>
  </transition>
</div>

3 Answers 3

5

Please take a look this solution.

Vue.component('transition-collapse-height', {
  template: `<transition
    enter-active-class="enter-active"
    leave-active-class="leave-active"
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
  >
    <slot />
  </transition>`,
  methods: {
    /**
     * @param {HTMLElement} element
     */
    beforeEnter(element) {
      requestAnimationFrame(() => {
        if (!element.style.height) {
          element.style.height = '0px';
        }

        element.style.display = null;
      });
    },
    /**
     * @param {HTMLElement} element
     */
    enter(element) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          element.style.height = `${element.scrollHeight}px`;
        });
      });
    },
    /**
     * @param {HTMLElement} element
     */
    afterEnter(element) {
      element.style.height = null;
    },
    /**
     * @param {HTMLElement} element
     */
    beforeLeave(element) {
      requestAnimationFrame(() => {
        if (!element.style.height) {
          element.style.height = `${element.offsetHeight}px`;
        }
      });
    },
    /**
     * @param {HTMLElement} element
     */
    leave(element) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          element.style.height = '0px';
        });
      });
    },
    /**
     * @param {HTMLElement} element
     */
    afterLeave(element) {
      element.style.height = null;
    },
  },
});

new Vue({
  el: '#app',
  data: () => ({
    isOpen: true,
  }),
});
.enter-active,
.leave-active {
  overflow: hidden;
  transition: height .5s linear;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <button @click="isOpen = !isOpen">
    is open: {{ isOpen }}
  </button>
  <transition-collapse-height>
    <div v-show="isOpen">
      <div>1</div>
      <div>2</div>
      <div>3</div>
    </div>
  </transition-collapse-height>
</div>

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

3 Comments

hey, thanks for your reply. Isn't this a little bit overkill? :) Was hoping to find a little CSS solution. I updated my question
If you define transition-collapse-height component, you can use this one in other code. I think it's good solution. How do you think of it.
I'm choosing this as the answer because it's the most dynamic one :)
3

use max-height for animation because we couldn't animate height prop according to this answer

new Vue({
  el: '#app',
  data: () => ({
    isOpen: false,
  }),
});
.expand-enter-active,
.expand-leave-active {
  transition: max-height .5s ease;
  max-height: 400px;
}

.expand-enter,
.expand-leave-to {
  max-height: 0;
  overflow: hidden;
}

.list {
  box-shadow: 1px 0 10px #aaa;
  padding: 8px;
  width: 64px;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <button @click="isOpen = !isOpen">
    Open
  </button>
  <transition name="expand">
    <div v-if="isOpen" class="list">
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>5</div>
    </div>
  </transition>
</div>

4 Comments

hey, thanks for your reply! Sorry, I'm not using Vue3 yet. This is a Vue2 app. I updated my code snippet in the question
@BoussadjraBrahim Can you please check your code again? I don't think it's working well.
Yeah, i deleted to from .expand-leave-to accidentally, please check it again
thanks for your solution. Unfortunately there are some hardcoded values in the css. I think this wasn't dynamic enough (when dealing with a loop generating a list of elements)
1

You can use keyframe for the enter/mount animation and then set height to 0 while unmount. If the height is not known, you can use max-height instead of height.

Vue.createApp({
  data() {
    return {
      isOpen: false
    };
  }
}).mount('#app')
.expand {
    height: 50px;
    animation: slideDown 1s linear;
    overflow: hidden;
}

.expand-leave-active.expand-leave-to {
    transition: height 1s ease;
    height: 0;
}

@keyframes slideDown{
    from {
        height: 0;
    }
    to {
        height: 50px;
    }
}
<script src="https://unpkg.com/vue@next"></script>

<div id="app">
  <button @click="isOpen = !isOpen">
    is open: {{ isOpen }}
  </button>
  <transition name="expand">
    <div v-if="isOpen" class="expand">
      <div>1</div>
      <div>2</div>
      <div>3</div>
    </div>
  </transition>
</div>

1 Comment

your answer is good but unfortunately there are some hardcoded values like height in the css

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.