Leon Csergity
Leon Csergity

Reputation: 313

CSS Transition on height not working when fully done from Javascript

I'm trying to make an animation for sliding up and sliding down. To toggle parts of the page. When the height is "hardcoded" in CSS to 210px before rendering the page and I call the Javascript function from a button it all works.

But when I try to do this dynamically, with Javascript to keep "hardcoding" to a minimum. It still does the change to the height. But the transition effect does not happen.

Here is the code snippet to high light the problem. I don't understand as to why this difference sabotages the transition.

function slideUp() {
    var target = document.getElementById("targetDiv");

    target.style.height = "" + target.clientHeight+"px"; // taking the rendered height of the div and setting it in CSS to mimic the pre set height in CSS 
    target.style.transition = "height 1.0s ease-in 0s";
    target.style.height = "0px";
}
.divStyle {
  /* height: 200px;  without this the animation does not work */
  background: blue;
  overflow: hidden;
}
<div id="targetDiv" class="divStyle">
    random content 
</div>


<button onclick="slideUp()"> Slide up </button>

Upvotes: 3

Views: 2237

Answers (4)

dawidl022
dawidl022

Reputation: 53

As mentioned before: having a height/max-height value set to auto/none stops the transition from working.

Rather than setting the max-height to a specified, larger than needed max-height by guesswork (if set to really high number causes a delay in the transition), I added transition and a max-height to the content wrapper instead, and used JavaScript to read the actual height of the contents. This way the wrapper max-height is set to the currently rendered height of its contents. This gives it a fixed inline height.

The problem with this solution, is that if the user decides to resize his window, into say, a narrower screen (i.e. rotating from landscape to portrait mode on mobile), the fixed max-height causes to content to be cut-off. The work-around for this is setting a setTimeout() function to set the max-height of the wrapper back to none just after the transition has ended. (none is the default, initial CSS value of the max-height property)

The same thing needs to happen before the "contracting" transition. The wrapper's max-height needs to be a fixed value for the transition to work. Therefore, the height needs to again be read from its contents (which might have actually changed in the meantime), applied to the wrapper, then after a short delay (50ms seemed to work in my case, and the delay wasn't noticeable) begin the transition.

Here's an example that expands the div contents when a button is pressed:

HTML:

<div id="content-wrapper">
  <div id="content">
    <!-- a lot of text content -->
  </div>
</div>
<button id="show-more" type="button">Show More</button>

CSS:

#content-wrapper {
  /* Initial max-height, can be any value including 0 */
  max-height: 270px;
  overflow: hidden;
  /* transition occurs on max-height change with specified delay; */
  transition: max-height 0.7s;
}

#content {
  /* This makes margin of the contents be respected by the container
    i.e. the height of the container its childrens' margins */
  overflow: hidden;
}

JS:

function resetHeight() {
  // make max-height of wrapper responsive again
  wrapper.style.maxHeight = "none";
}


function toggleText() {
  // check if content-wrapper is open
  if (wrapper.className.includes("open")) {
    // if is open, close content-wrapper and set initial max-height to wrapper
    let contentHeight = window.getComputedStyle(content).height;
    // sets max-height back from none to computed content height
    wrapper.style.maxHeight = contentHeight;
    // give it some time to reset the transition trigger caused by previous statement
    setTimeout(function() {
      wrapper.classList.remove("open");
      wrapper.style.maxHeight = "270px"; // reset to our initial max-height
      button.textContent = "Show More";
    }, 50)
  } else {
    // if not open, open content-wrapper then use content's height to set the
    // max-height of wrapper
    wrapper.classList.add("open");
    let contentHeight = window.getComputedStyle(content).height;
    wrapper.style.maxHeight = contentHeight;
    button.textContent = "Show Less";
    setTimeout(resetHeight, 700) // the timeout is the same as the transition time
  }
}

const content = document.getElementById("content")
const wrapper = document.getElementById("content-wrapper");
const button = document.getElementById("show-more");
button.addEventListener("click", toggleText);

This is my first answer on StackOverflow, so if there is anything I can improve please do let me know.

Upvotes: 0

Leon Csergity
Leon Csergity

Reputation: 313

Just so that if anyone comes along here, the given answers are good, just not in the current case I need. The way I managed finally is as follows.

function slideDown( targetId ){
    var target = document.getElementById(targetId);
    target.style.height =  target.children[0].clientHeight + "px";
}

function slideUp( targetId ) {
    var target = document.getElementById(targetId);
    target.style.height = "0px";
}
.parentDivStyle{
  overflow: hidden;
  transition: height 0.5s ease-in 0s;
  height: 0px;
  background: blue;
}

.childDivStyle {
  background: green;
  padding: 10px 10px 10px 10px;
}
<div id="parent" class="parentDivStyle">
    <div class="childDivStyle">
      random stuff
      text text text...........
    </div>
</div>

<button onclick="slideUp('parent')"> Slide Up </button>
<button onclick="slideDown('parent')"> Slide Down </button>

This was actually a comment in a deleted answer. Idk why it was deleted. Was useful.

Upvotes: 1

Ullas Hunka
Ullas Hunka

Reputation: 2211

There are multiple ways to achieve the goal, in your case you just require to init the style which is supposed to change at the later time so just use window.onload and init the value.

var org = "";
window.onload = function() {
  var target = document.getElementById("targetDiv");

  org = target.clientHeight;
  target.style.height = "" + target.clientHeight + "px";
}

function slideUp() {
  var target = document.getElementById("targetDiv");

  target.style.transition = "height 1.0s ease-in 0s";
  if (target.clientHeight == org) {
    target.style.height = "0px";
  } else {
    target.style.height = org + "px";
  }
}
.divStyle {
  /* height: 200px;  without this the animation does not work */
  background: blue;
  overflow: hidden;
}
<div id="targetDiv" class="divStyle">
  random content
</div>


<button onclick="slideUp()"> Slide up </button>

Upvotes: 1

wscourge
wscourge

Reputation: 11291

Use of max-height combined with setTimeout "hack" worked for me:

function slideUp() {
  
  var target = document.getElementById("targetDiv");
  
  target.style.maxHeight = target.clientHeight + "px";
  
  setTimeout(function() {
    target.style.maxHeight = 0;
  }, 10);

}
.divStyle {
  background: blue;
  overflow: hidden;
  transition: max-height 1s ease-in 0s;
}
<div id="targetDiv" class="divStyle">
    random content 
</div>
<button onclick="slideUp()"> Slide up </button>

However, it is not a clean way. Think of using transform: scaleY(0) instead.

Upvotes: 3

Related Questions