6

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.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
Joey
  • 10,504
  • 16
  • 39
  • 54

5 Answers5

27

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 :(
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 1
    Just awesome @roko-c-buljan. Thanks for the code and for the comments that help a lot :) – Greg Oct 04 '17 at 14:54
  • 1
    omg this is so genius...thanks a lot!!!!!! i was wrestling with max-height and setTimeout until i found your supersmart approach :) – nerdess Mar 08 '18 at 09:10
2

I try it by detect the base height of my element at first and use it to work with transition, because the transition need height to work .

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

var height = box.offsetHeight;
box.style.height = height+"px";

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

<div id="box">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
</div>
Maher
  • 2,517
  • 1
  • 19
  • 32
1

Try this:

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

//get your dynamic div's height:
var clientHeight = document.getElementById('box').clientHeight;
var expanded = true;
//define max-height property your div before collapse it. 
//otherwise it collapse without transition.
box.style.maxHeight = clientHeight+"px";
button.addEventListener('click', function() {
    if (expanded) {
        box.style.maxHeight = 0;
        expanded = false;
    } else {
        //set height
        box.style.maxHeight = clientHeight+"px";
        expanded = true;
    }
});

You can use .scrollHeight or .offsetHeight instead of .clientHeight depends on the situation. Check this comment for detailed info about these properties: https://stackoverflow.com/a/22675563/7776015

gld-R
  • 11
  • 3
0

Something along these lines should do the trick:

<div id="box">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
</div>
#box {
  margin-top: 20px;
  border: 1px solid black;
  background-color: #4f88ff;
  height:0px;
  width: 300px;
  transition: height 0.25s;
  overflow: hidden;
}

Do the logic when requesting an animation frame:

 requestAnimationFrame(function () {
    let boxDiv = document.getElementById("box");
    boxDiv.style.height = "200px";
   });
Ravi L
  • 1,142
  • 9
  • 17
  • Why hardcode arbitrary heights like "200px"? This does **not toggles** (expand/collapse) as requested by OP. It's error prone, and hard to maintain. – Roko C. Buljan Jun 03 '22 at 15:11
-1

You have 2 options:

  • Transition the border and padding so that they all collapse together.

  • Wrap the target div in a new container, and animate the height of that. You'll need overflow: hidden;.

sidewinder
  • 682
  • 4
  • 9