2

I've been searching for a long time for a solution to this problem, but I can't seem to find the best solution for this problem. Basically I have an accordion which when a user open's it, it should slide down gently with a nice transition.

I'm pretty sure this is possible with only css. Please correct me if I'm wrong, but css transitions are performance wise better than js transitions.

So now the the HTML for the problem I'm facing:

<transition name="slide-down">
  <ul v-if="isDisplayingOpeningHours" class="mt-2 text-sm flex flex-col space-y-2">
    <li v-for="i in 10" :key="i"> <!-- just for testing purposes -->
      element
    </li>
  </ul>
</transition>

And the css is:

.slide-down-enter-active, .slide-down-leave-active {
  transition: all .5s;
}

.slide-down-enter-to, .slide-down-leave {
  overflow: hidden;
  max-height: 100%;
}

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

This results in a not fluent transition. Sliding down doesn't seem to be a problem, but when it slides up it's really janky and not fluent at all!. If I set the max-height: 10rem it isn't making it any better. I'm hoping someone can debug help me with this problem and maybe educate me more about vue transitions and js vs css transitions.

Edit:
Just for clarification the transition itself should by css only. The opening of the accordion is with js which is fine. Also it would be nice to maybe see an example of a transition use with the native vue-transition component.

Edit 2:
Here is a codesandbox which has the same problem I have. https://codesandbox.io/s/frosty-bash-lmc2f?file=/src/components/HelloWorld.vue

Ezrab_
  • 825
  • 5
  • 19
  • 44

1 Answers1

6

Another Update!!! In order to achieve JS transition you can take a look at official document to learn. Anyways, I am not going to implement fancy timing mechanism, but in simplest form, JS collapse transition in react will look something like following, I included it in existing codesandbox:

<template>
...
  <div>
    <button @click="jsOpen = !jsOpen">Open JS list</button>
    <transition
      v-on:before-enter="beforeEnter"
      v-on:enter="enter"
      v-on:after-enter="afterEnter"
      v-on:enter-cancelled="enterCancelled"
      v-on:before-leave="beforeLeave"
      v-on:leave="leave"
      v-on:after-leave="afterLeave"
      v-on:level-cancelled="leaveCancelled"
      v-bind:css="false"
    >
      <ul v-if="jsOpen" class="mt-2 text-sm flex flex-col space-y-2">
        <li v-for="i in 10" :key="i">item-{{ i }}</li>
      </ul>
    </transition>
  </div>
</template>

<script>
export default {
  ...
  methods: {
    beforeEnter(el) {
      el.style.height = 0;
      el.style.overflow = "hidden";
    },
    enter(el, done) {
      const increaseHeight = () => {
        if (el.clientHeight < el.scrollHeight) {
          const height = `${parseInt(el.style.height) + 5}px`;
          el.style.height = height;
        } else {
          clearInterval(this.enterInterval);
          done();
        }
      };
      this.enterInterval = setInterval(increaseHeight, 10);
    },
    afterEnter(el) {},
    enterCancelled(el) {
      clearInterval(this.enterInterval);
    },
    beforeLeave(el) {},
    leave(el, done) {
      const decreaseHeight = () => {
        if (el.clientHeight > 0) {
          const height = `${parseInt(el.style.height) - 5}px`;
          el.style.height = height;
        } else {
          clearInterval(this.leaveInterval);
          done();
        }
      };
      this.leaveInterval = setInterval(decreaseHeight, 10);
    },
    afterLeave(el) {},
    leaveCancelled(el) {
      clearInterval(this.leaveInterval);
    },
  },
};
</script>

Update: After understanding requirements from OP, I was trying to play with different things and I stumbled upon this article on css-tricks which explains in much more details about transition, from that article another non-js solution proposed would look like following, I included it in existing stackblitz:

.scale-enter-active,
.scale-leave-active {
  transform-origin: top;
  transition: transform 0.3s ease-in-out;
}

.scale-enter-to,
.scale-leave-from {
  transform: scaleY(1);
}

.scale-enter-from,
.scale-leave-to {
  transform: scaleY(0);
}

here instead of height, it is scaling against y-axis. IT also has a side effect that it expands container to full size first and than updates (scales) content afterwards.

Vue v3.x -leave and -enter classes in V3 are renamed to -leave-from and -enter-from. Take a look at this stackblitz.

.slidedown-enter-active,
.slidedown-leave-active {
  transition: max-height 0.5s ease-in-out;
}

.slidedown-enter-to,
.slidedown-leave-from {
  overflow: hidden;
  max-height: 1000px;
}

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

Vue v2.x Problem is you are specifying max-height to 100%, use some value which will be greater than your expected max-height. Update your style to:

.slide-down-enter-active,
.slide-down-leave-active {
  transition: max-height 0.5s ease-in-out;
}

.slide-down-enter-to,
.slide-down-leave {
  overflow: hidden;
  max-height: 1000px;
}

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

and it should work. Take a look at this codesandbox demo.

tony19
  • 125,647
  • 18
  • 229
  • 307
Dipen Shah
  • 25,562
  • 1
  • 32
  • 58
  • Okay, could I set the height to auto? Since I don't want to set a fixed height. – Ezrab_ Oct 05 '20 at 18:12
  • @Ezrab_ You could but it will not work as it will require some height value. Why don't you have a scrollable fixed height container? – Dipen Shah Oct 05 '20 at 18:14
  • well, because it shouldn't be scrollable. Also as seen from the example it's still not smooth. (I've implemented the solution you gave me) https://gyazo.com/020835748ef0443fd4f4f76db8321d0e – Ezrab_ Oct 05 '20 at 19:21
  • @Ezrab_ updated my answer, trick is to use max-height to concrete value which will exceed your expected maximum height. – Dipen Shah Oct 05 '20 at 19:27
  • Updated the code, but still not smooth! https://gyazo.com/de52bac75df2c7a06c0694ff9421694a – Ezrab_ Oct 05 '20 at 19:36
  • @Ezrab_ that seems strange, would you be able to create codesandbox as codesandbox I shared is working fine. For maximum smoothness, we need correct height without that it will mot be perfect but would be smooth than you have in your video. – Dipen Shah Oct 05 '20 at 19:39
  • Also, try with `transition: max-height 0.5s ease-in-out` and see if it helps in anyway. – Dipen Shah Oct 05 '20 at 21:00
  • @Ezrab_ problem was Vue version we both were using was different, take a look at updated link and code. – Dipen Shah Oct 06 '20 at 19:29
  • Awesome for finding that out, but it's still not very smooth. Is it possible to use a max-height of 100%? Or is it absolutely impossible to make this smooth without the use of javascript? – Ezrab_ Oct 07 '20 at 07:17
  • I am not sure what do you mean by "smooth" as codesandbox you shared is working smoothly on my machine. – Dipen Shah Oct 07 '20 at 08:09
  • Are you not seeing any lag when the list is open en you want to collapse it again? – Ezrab_ Oct 07 '20 at 11:27
  • @Ezrab_ but that is expected because your max-height is so big that it takes time to animate height that is not visible to the user. You can play with `max-height` and your will see with small value, transition works perfectly fine. Key is to have **right height**. – Dipen Shah Oct 07 '20 at 13:11
  • Added one more update but CSS-only solution will always have limitation unless you used fixed height. – Dipen Shah Oct 07 '20 at 13:48
  • Ok, can you show me an example where it’s smooth with js? – Ezrab_ Oct 07 '20 at 21:29
  • Did you look at react-bootstrap?https://react-bootstrap.github.io/utilities/transitions/ – Dipen Shah Oct 07 '20 at 21:52
  • I don't mean just a component in React. But actual code that works with Vue transition to accomplish it. – Ezrab_ Oct 08 '20 at 18:44
  • Maybe you could explain your JS, and show us how to increase the speed for example. Also there is a bug if the user clicks the button while the transititon is going down it will infinitely try to go up and down. https://gyazo.com/a0495403aced8591c7eb5f7ab46a475f – Ezrab_ Oct 09 '20 at 11:08
  • 1
    Sure it is just increasing and decreasing elements style and to increase speed you update `setInterval` to use small value and to decrease use bigger one. I will take a look later and update codesandbox. – Dipen Shah Oct 09 '20 at 12:38
  • @Ezrab_ checkout my updates, I am clearing interval on enter/leave cancelled event handler to ensure we don't have any pending event handler calls. – Dipen Shah Oct 09 '20 at 16:43