-1

So what I want is to have a smooth transition from height: 0; to height: auto; revealing the content inside a <div></div>. The best method I've seen so far is using the max-height property, and with that you get this.

#menu #list {
  max-height: 0;
  transition: max-height 0.15s ease-out;
  overflow: hidden;
  background: #d5d5d5;
}

#menu:hover #list {
  max-height: 500px;
  transition: max-height 0.25s ease-in;
}
<div id="menu">
  <a>hover over me</a>
  <ul id="list">
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
  </ul>
</div>

And this works perfectly, except the data I have inside the list is dynamic and is taken from a database. So it can have a large number of items or quite few. I can't set an absurdly high number as the max-height because while that would work fine for a large number of items, it would be instantaneous for few items (transitioning the max-height from 0px to 99999px would not show animation if there are only 3 items). Apart from the number of items, they would also be displayed differently on different devices, so setting the max-height by the number of items using JavaScript would also be quite difficult. While this solution is good, it isn't quite perfect.

Is there a solution that would work if there are a large number of items as well as if there were a few? A pure CSS solution would be simply perfect! But if that isn't possible, then I'm alright with a JavaScript one.

Sujit
  • 1,653
  • 2
  • 9
  • 25
  • set a fixed max-height and give overflow: scroll – Khn Rzk Jun 03 '21 at 07:50
  • Are you looking for a pure CSS solution? Because there's a trick that can be done with a bit of JS – Yoav Kadosh Jun 03 '21 at 07:50
  • @RifkanRazak that would work, but it's not what I'm looking for. – Sujit Jun 03 '21 at 07:51
  • @YoavKadosh I'm open to JavaScript solutions as well, maybe I should add that tag. – Sujit Jun 03 '21 at 07:51
  • You can do the reverse I guess, give a min-height which may go smooth with all devices – Khn Rzk Jun 03 '21 at 07:56
  • @RifkanRazak I don't think that would work. Maybe an explanation? – Sujit Jun 03 '21 at 07:57
  • for the dropdown div, set data on scroll only instead of loading all at once – Khn Rzk Jun 03 '21 at 07:58
  • @RifkanRazak I don't want the user to have to scroll inside the div, that wouldn't look good. Ideally I'd like to set `max-height` to be set to automatically fit the content inside the `
    `. Something like `height: auto;`.
    – Sujit Jun 03 '21 at 08:03
  • You can do this without JS - have a pseudo element cover the list and then on hover transition to height 0 from height 100% (to the bottom). – A Haworth Jun 03 '21 at 08:53
  • 2
    The question pointed to has a gazillion (well, 53) answers, most of which do not answer the question as set as they use JS (or even worse, expect you to load jQuery). For those landing here who want to go straight to a CSS solution, look at the 2020 one which uses animating clip path, even simpler than my pseudo element suggestion. – A Haworth Jun 03 '21 at 09:27
  • @AHaworth can you give a link to that post? Because the answer I wanted is **NOT** the one that was marked as the solution in that question and yet this was marked as a freaking duplicate ‍♂️. – Sujit Jun 03 '21 at 13:01
  • The answer that I thought might help you is at [link]https://stackoverflow.com/questions/3508605/how-can-i-transition-height-0-to-height-auto-using-css/60363334#60363334 from @AndriiKovalenko using clip path animation. If that doesn't work I'll try to resurrect my own solution which I was typing in when your question was closed! – A Haworth Jun 03 '21 at 13:13
  • @AHaworth I see, but this doesn't give the animation while moving the mouse away, transition does that. To implement that in animation would require JavaScript too. Ah I voted to re-open this question, but it requires 3 people in total to do so :( Thanks tho! – Sujit Jun 03 '21 at 13:20
  • @Paulie_D could you point out the answer in the question pointed to as I can't find one that does exactly what was wanted without JS (there's so many answers I may well have missed it). Thanks. – A Haworth Jun 03 '21 at 13:22

2 Answers2

1

You can use JavaScript for this. You can use childElementCount to get count of all li elements in ul and set maxHeight based upon it.

let ul = document.querySelector("#list")

document.querySelector("#menu").onmouseover = function (){
  ul.style.maxHeight = 100*ul.childElementCount + "px";
};

document.querySelector("#menu").onmouseleave = function (){
  ul.style.maxHeight = 0;
};
#menu #list {
  max-height: 0;
  transition: max-height 0.15s ease-out;
  overflow: hidden;
  background: #d5d5d5;
}

#menu:hover #list {
  transition: max-height 0.25s ease-in;
}
<div id="menu">
  <a>hover over me</a>
  <ul id="list">
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
  </ul>
</div>
TechySharnav
  • 4,869
  • 2
  • 11
  • 29
  • This would work, but I mentioned I was not looking for a solution involving the number of items as I display them differently on different devices. Definitely deserves an upvote tho! – Sujit Jun 03 '21 at 08:22
  • @Sujit Then, you need to go with `scrollHeight` as said by Yoav. Thanks for the upvote! – TechySharnav Jun 03 '21 at 08:31
1

It's possible to do this with a bit of JS.

Set the transition to work on the height property. When you hover (i.e. mouseenter), you apply a height value based on the scrollHeight of the content.

as soon as the transition is completed you set the height to auto so that further changes in height are automatically applied (for example, if another item is added while the list is still open). This essentially creates a transition between 0 and auto.

On mouseleave you set the height to the scrollHeight again (since we can't transition from auto), wait for the browser to paint (with setTimeout with 0 delay or requestAnimationFrame), and then back to 0.

const list = document.getElementById('list');
const trigger = document.getElementById('trigger');
let timeout;

trigger.addEventListener('mouseenter', () => {
  list.style.height = `${list.scrollHeight}px`;
  // Remove the hardcoded height at the end of the transition
  // so that further changes to the height will be automatically
  // reflected
  clearTimeout(timeout);
  timeout = setTimeout(() => list.style.height = 'auto', 150); // Should match the transition delay
});

trigger.addEventListener('mouseleave', () => {
  clearTimeout(timeout);
  list.style.height = `${list.scrollHeight}px`; // Set it to the height again
  timeout = setTimeout(() => list.style.height = 0, 0); // Wait for paint
});
#list {
  height: 0;
  transition: height 0.15s ease-out;
  overflow: hidden;
  background: #d5d5d5;
}
<div id="menu">
  <a id="trigger">hover over me</a>
  <ul id="list">
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
  </ul>
</div>
Yoav Kadosh
  • 4,807
  • 4
  • 39
  • 56
  • Why is setting `height` to `auto` required? – Sujit Jun 03 '21 at 08:15
  • Because if we leave it with the hardcoded height, and let say another item is added when the list is still visible, the height will not change. – Yoav Kadosh Jun 03 '21 at 08:16
  • Ah I see. And what about setting it back to 0 in the `setTimeout()` function with no delay? – Sujit Jun 03 '21 at 08:20
  • That's done in order to wait for the browser to paint. If we don't do this, the height will go to 0 before the previous height has been painted and there will be no transition. This can also be done with `requestAnimationFrame`. – Yoav Kadosh Jun 03 '21 at 08:22
  • 1
    You don't need to "wait", simply force a reflow. Your mouseleave would become `list.style.height = \`${list.scrollHeight}px\`; document.body.offsetLeft; list.style.height = 0;` https://stackoverflow.com/questions/55134528/css-transition-doesnt-start-callback-isnt-called/55137322#55137322 – Kaiido Jun 03 '21 at 08:53
  • @Kaiido nice trick – Yoav Kadosh Jun 03 '21 at 08:58