Don't use max-height
...well, unless you want to adopt the CSS-only sloppy solution that generates delay gap issues - ...that's why you recurred to JS in the first place, hopefully
so, use height
and transition: height
instead
function slidetoggle() {
document.querySelectorAll(this.getAttribute('data-slidetoggle')).forEach(el => {
const ch = el.clientHeight,
sh = el.scrollHeight,
isCollapsed = !ch,
noHeightSet = !el.style.height;
el.style.height = (isCollapsed || noHeightSet ? sh : 0) + "px";
if (noHeightSet) return slidetoggle.call(this);
});
}
document.querySelectorAll("[data-slidetoggle]").forEach(el => el.addEventListener('click', slidetoggle));
#box1,
#box2 {
overflow: hidden;
margin-bottom: 20px;
background-color: #4f88ff;
width: 200px;
transition: height 0.5s;
}
<button data-slidetoggle="#box1">Toggle Box 1</button>
<div id="box1">
Hello World<br> This is dynamic content<br> The actual height won't be<br> known ahead of time
</div>
<button data-slidetoggle="#box2">Toggle Box 2</button>
<div id="box2">
Hello World<br> This is dynamic content<br> The actual height won't be<br> known ahead of time<br> bla
<br> bla
<br> bla
</div>
TIP: if you need paddings, my suggestion is to wrap your content into another child box - with paddings.
The code above is a conceptual and minimal - with lots of room for improvements, but hopefully you got the idea.
Explanation of why the above, and why not to use the following:
CSS height-transition cannot work if we don't set an initial height.
So basically on click - if such height does not exists we need to set it via JS.
After such height is set the transition will still not work because the browser did not had the time to calculate the box model of such element - to transition-from:
// (INVALID DEMO)
//
// Say the element is expanded.
// We need a height to transition-from
el.height = calculatedHeight +"px"; // set CSS height to the calculated value
// Transition to 0px height....
el.height = 0 +"px";
// NO DEAL. our element just snapped to 0 without transition
therefore we need to reside to some kind of callback.
One type of common (but sloppy way) is to use a setTimeout()
callback
// (INVALID DEMO)
//
// Say the element is expanded.
// We need a height to transition-from
el.height = calculatedHeight +"px"; // set CSS height to the calculated value
// Transition to 0px height giving the browser some ready-state hack
setTimeout(function() {
// Eventually the box model will be calculated
el.height = 0 +"px";
}, 0); // <<< PROBLEM: Should we use 0 ? 10? 100? 1000?
// and our element beautifully transitioned to 0 height.
// Eventually :(