Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating max-height with pure JS?

I want to animate the height of a div. This is normally done in CSS by animating the max-height property.

However I need to do this in JS. The div is populated with dynamic content that changes frequently so the actual height is not known ahead of time.

Here is a jsfiddle: https://jsfiddle.net/vpknxuk8/

var box = document.getElementById("box");
var button = document.getElementById("button");
var expanded = true;

button.addEventListener('click', function() {
    if (expanded) {
        box.style.maxHeight = 0;
        expanded = false;
    } else {
        box.style.maxHeight = "";
        expanded = true;
    }
});
#box {
  margin-top: 20px;
  border: 1px solid black;
  background-color: #4f88ff;
  width: 200px;
  transition: max-height 0.25s linear;
  overflow: hidden;
}
<button id="button">Click</button>

<div id="box">
  Hello World<br>
  This is dynamic content<br>
  The actual height won't be known ahead of time
</div>

The box should transition between a height of 0 and its default height, but for some reason it isn't animating.

The only other versions of this question on StackOverflow pertain to CSS-only solutions or jQuery solutions.

like image 415
Joey Avatar asked Jun 09 '17 23:06

Joey


1 Answers

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 :(
like image 168
Roko C. Buljan Avatar answered Oct 02 '22 21:10

Roko C. Buljan