26

I use transition: height 500ms to add an animation to an element that slides open via button from height: 0 to height: 100px and vice versa.

Since the element's content is added dynamically and I don't know its size I would like to switch to height: fit-content instead. This way the element would have always the right size to show its content.

Sadly this disables the animation.

How can I get the animation together with a div element which size fits its content?

Following snippet shows the behavior:

document.querySelector('button')
  .addEventListener(
    'click',
    () => document.querySelectorAll('div')
      .forEach(div => div.classList.toggle('closed')));
div {
  background-color: lightblue;
  border: 1px solid black;
  overflow: hidden;
  transition: height 500ms;
}

div.closed {
  height: 0 !important;
}

div.div1 {
  height: 100px;
}

div.div2 {
  height: fit-content;
}
<button type="button">toggle</button>

<h1>'height: 100px' => 'height: 0'</h1>
<div class="div1">
some text<br />
even more text<br />
so much text
</div>

<br>

<h1>'height: fit-content' => 'height: 0'</h1>
<div class="div2">
some text<br />
even more text<br />
so much text
</div>
danronmoon
  • 3,814
  • 5
  • 34
  • 56
Sebastian Barth
  • 4,079
  • 7
  • 40
  • 59
  • fit-content is actually a function which IIRC is not an animate-able value. – TylerH Sep 14 '18 at 18:49
  • There's no elegant pure CSS solution, jQuery can solve it with ease (.slideToggle()). – VXp Sep 14 '18 at 19:04
  • 2
    Possible duplicate of [How can I transition height: 0; to height: auto; using CSS?](https://stackoverflow.com/questions/3508605/how-can-i-transition-height-0-to-height-auto-using-css) – VXp Sep 14 '18 at 19:06

8 Answers8

9

As Mishel stated another solution is to use max-height. Here is a working example of that solution.

The key is to approximate your max-height when it is fully expanded, then the transitions will be smooth.

Hope this helps.

https://www.w3schools.com/css/css3_transitions.asp

document.querySelector('button')
  .addEventListener(
    'click',
    () => document.querySelectorAll('div')
      .forEach(div => div.classList.toggle('closed')));
div {
  background-color: lightblue;
  border: 1px solid black;
  overflow-y: hidden;
 max-height: 75px; /* approximate max height */
 transition-property: all;
 transition-duration: .5s;
 transition-timing-function: cubic-bezier(1, 1, 1, 1);
}
div.closed {
   max-height: 0;
}
<button type="button">toggle</button>

<h1>'height: 100px' => 'height: 0'</h1>
<div class="div1">
some text<br />
even more text<br />
so much text
</div>

<br>

<h1>'height: fit-content' => 'height: 0'</h1>
<div class="div2">
some text<br />
even more text<br />
so much text
</div>
Roskow
  • 320
  • 1
  • 7
5

one possible solution, though not perfect, is to animate font-size instead of height.

another solution might be to animate max-height instead of height. you can use max-height say 300px or 500px. but if you require more than that, it won't look good.

here I'm animating font-size.

Hope that helps. Thanks.

document.querySelector('button')
  .addEventListener(
    'click',
    () => document.querySelectorAll('div')
      .forEach(div => div.classList.toggle('closed')));
div {
  background-color: lightblue;
  border: 1px solid black;
  overflow: hidden;
  transition: font-size 500ms;
}

div.closed {
  font-size: 0 !important;
}

div.div1 {
  font-size: 14px;
}

div.div2 {
  font-size: 14px;
}
<button type="button">toggle</button>

<h1>'height: 100px' => 'height: 0'</h1>
<div class="div1">
some text<br />
even more text<br />
so much text
</div>

<br>

<h1>'height: fit-content' => 'height: 0'</h1>
<div class="div2">
some text<br />
even more text<br />
so much text
</div>
Mishel Tanvir Habib
  • 1,112
  • 11
  • 16
  • I indeed used this method for another situation where the content were elements itself: As you are scaling the text element I scaled the child elements. Thanks for the reminder! – Sebastian Barth Sep 14 '18 at 21:20
  • Works perfectly for my dropdown menus of various heights. Thanks! – Zhiyong Jul 14 '20 at 18:18
0

The way I solved my problem was Calculating the height of the children

document.querySelector('.button1').addEventListener('click',
  () => document.querySelector('.div1').classList.toggle('closed'));

document.querySelector('.button2').addEventListener('click',
  () => document.querySelector('.div2').classList.toggle('closed'));

let extendedHeight = 0;
let div2 = document.querySelector('.div2')
for (let i = 0; i < div2.children.length; i++) {
  extendedHeight += div2.children[i].offsetHeight;
}

div2.style.setProperty('--extended-height', extendedHeight.toString() + "px")
div {
  background-color: lightblue;
  border: 1px solid black;
  overflow: hidden;
  transition: 500ms;
}

div.div1.closed {
  max-height: 0 !important;
}

div.div1 {
  max-height: 1000px;
}

div.div2.closed {
  height: 0 !important;
}

div.div2 {
  --extended-height: 0px;
  height: var(--extended-height);
}
<button type="button" class="button1">toggle</button>

<h1>'height: over predicted' => 'height: 0'</h1>
<div class="div1">
  <p>
  some text <br> even more text <br> so much text <br><br>
  </p>
</div>

<button type="button" class="button2">toggle</button>

<h1>'height: calculated' => 'height: 0'</h1>
<div class="div2">
  <p>some text <br> even more text <br> so much text <br><br>
  </p>
</div>

If the height is over predicted then the animation will look broken, but if the height is perfectly calculated and set the animation will work correctly and look perfect.

To do this I calculated the height of all of the children of the div using the offsetHeight which returns the height of the element + padding-top + border-top + margin-top

I then set the custom CSS variable that I made which is --extended-height to the height of all the children

You might get a problem if the div has different elements that are positioned beside each other, and the way I solved that was only getting the offsetHeight of the biggest elements of the elements that are positioned beside each other. In my case I just used the offsetHeight of every other element

Thanks

ZiyadCodes
  • 379
  • 3
  • 10
0

you can try using tranform: scaleY(0) -> scale(1) its kinda does the same thing as height.

fruitloaf
  • 1,628
  • 15
  • 10
0

I use this:

function slide(button, event) {
            document.getElementById("container").classList.toggle("container-opened");
        }
.container {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    height: 0;
    overflow: hidden;
    transition: 1s all;
}

.card {
    margin: 10px 5%;
    padding: 10px;
    background-color: #CCC;
}

.container-opened {
    height: 150vh;
    box-shadow: 1px 1px 10px 1px #CCC;
}
<button onclick="slide(this, event)">Slide!!</button>
    <div id="container" class="container">
        <article class="card">
            <p>I'm a children!!</p>
        </article>

        <article class="card">
            <p>I'm a children!!</p>
        </article>

        <article class="card">
            <p>I'm a children!!</p>
        </article>

        <article class="card">
            <p>I'm a children!!</p>
        </article>
    </div>

Just make sure the height is greater than the content itself occupies, and the content will automatically distribute itself. This can also be used with flex-direction: row, manipulating width instead of height, just making small changes to justify-content and align-items

Joaoalen
  • 1
  • 1
0

Considering that heights can vary considerably between mobile and desktop versions of the site, I didn't like the max-height solution for transitioning height from 0 to fit-content because if I underestimated it would cut content off, and if I overestimated the transition looked weird.

Here's my combined CSS + JS solution:

//CSS
div {
    overflow: hidden;
    transition: all 220ms;
}    

div:not(.active) {
    height: 0px !important;
}


//JS
div.addEventListener('click', function(e) {
   div.style.height = `${div.scrollHeight}px`;            
   div.classList.add('active');
});
egekhter
  • 2,067
  • 2
  • 23
  • 32
0

use the max height like others are saying but just make max-height: fit-content; and it should work as expected.

  • and use a percent for the height instead of px – dirtbowl Jun 30 '23 at 01:23
  • height: 0% max-height: fit-content hover{ height: 100% – dirtbowl Jun 30 '23 at 01:24
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 03 '23 at 18:25
0

I made a solution mixing filter and transform properties, which achieves a good looking effect:

    .dropdown-content {
        width: fit-content;
        transform: scaleY(0);
        filter: brightness(1000%);
        transform-origin: top;
        transition: all 0.5s;
    }
    .dropdown-content.visible {
        transform: scaleY(1);
        filter: none;
    }

This eliminates the effect of shrinking that scaleY() gives. The class "visible" should be toggled via JavaScript

alejo742
  • 151
  • 3