How can I transition height: 0; to height: auto; using CSS?
Asked Answered
B

46

2892

I am trying to make a <ul> slide down using CSS transitions.

The <ul> starts off at height: 0;. On hover, the height is set to height:auto;. However, this is causing it to simply appear, not transition,

If I do it from height: 40px; to height: auto;, then it will slide up to height: 0;, and then suddenly jump to the correct height.

How else could I do this without using JavaScript?

#child0 {
  height: 0;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent0:hover #child0 {
  height: auto;
}
#child40 {
  height: 40px;
  overflow: hidden;
  background-color: #dedede;
  -moz-transition: height 1s ease;
  -webkit-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
#parent40:hover #child40 {
  height: auto;
}
h1 {
  font-weight: bold;
}
The only difference between the two snippets of CSS is one has height: 0, the other height: 40.
<hr>
<div id="parent0">
  <h1>Hover me (height: 0)</h1>
  <div id="child0">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
<hr>
<div id="parent40">
  <h1>Hover me (height: 40)</h1>
  <div id="child40">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
Babita answered 18/8, 2010 at 2:50 Comment(12)
I believe the height:auto/max-height solution will only work if you're expanding area is greater than the height you want to restrict. If you have a max-height of 300px, but a combo box dropdown, which can return 50px, then max-height won't help you, 50px is variable depending on the number of elements, you can arrive to an impossible situation where I can't fix it because the height is not fixed, height:auto was the solution, but I can't use transitions with this.Flowered
OP is trying for css solution, not js, otherwise they could just use overflow and animateCralg
@VIDesignz: But inner div's -100% margin-top receives the width of the wrapper div, not the height. So this solution has the same kind of problem, that the max-height solution. Moreover when the width is smaller than the height of the content, not all content is hidden by -100% margin-top. So this is a wrong solution.Kumar
See github.com/w3c/csswg-drafts/issues/626 for discussion around the spec and implementation of a proper solutionBeulabeulah
@Paulie_D The question is about height, not width. All the answers are about height. Please don't change the question title to be about something it's not.Bungalow
@Bungalow the issue and answer are the same whether it's height or width so as a duplicate target the title should reflect that. Hence the change for clarity.Atelectasis
@Atelectasis I understand the desire to close questions as dupes of this but the issue is that the answers and question don't address or even mention width at all. There are probably plenty of good dupe target candidates for one that focuses on width, instead. Alternatively, you could put in some effort with the question and top answer here to cover width as well in a comprehensive way, and then such a title would be appropriate. At the end of the day, a question's title should reflect what the question asks, not what you want to close duplicates against.Bungalow
I'm not going to get into a rollback fight but width issues are ALREADY being closed as a duplicate of this question. Changing the title does not invalidate the existing question or answers so a canonical properly titled duplicate is appropriate. #34308646Atelectasis
@Atelectasis Here's a good one: #38644029 I'm not sure why you say "already being closed" and then point to an example from 5 years ago. That's just evidence that a poor target was used. Luckily there's no hard limit on editing dupe targets for gold badge holders so I can take some time today perhaps and fix some of the ones pointing to this one which are about width.Bungalow
The flexbox/grid way: keithjgrant.com/posts/2023/04/transitioning-to-height-autoGut
@Gut Thanks a lot!!!!!! I really really really thank you!!!!!Swineherd
chriscoyier.net/2022/12/21/…Doig
H
3645

Use max-height in the transition and not height. And set a value on max-height to something bigger than your box will ever get.

See JSFiddle demo provided by Chris Jordan in another answer here.

#menu #list {
    max-height: 0;
    transition: max-height 0.15s ease-out;
    overflow: hidden;
    background: #d5d5d5;
}

#menu:hover #list {
    max-height: 500px;
    transition: max-height 0.25s ease-in;
}
<div id="menu">
    <a>hover me</a>
    <ul id="list">
        <!-- Create a bunch, or not a bunch, of li's to see the timing. -->
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>
Hourihan answered 30/11, 2011 at 18:42 Comment(15)
this works great! except there is a delay when it starts, because it starts for max-height which initially is very high..hmm, i think this is somewhat annoyingWillena
+1 Great solution! The speed of the transition is calculated is calculated as the time you specify to transition to the max-height value... but since height will be less than max-height, the transition to actual height will occur faster (often significantly) than the time specified.Fastigium
The transition occurring at a slower rate as @Fastigium mentions could also be a good thing. Say you're expanding a menu, the menu will expand at a constant rate for each menu item, so expanding a menu with 9 items will take 9 times longer than a menu with 1. Regular behaviour when using height would the menu with 1 item expand significantly slower than the one with 9. May be desirable depending on the situation.Cracow
Note that this may cause ugly transition ending when you have to use values that are much bigger than the actual computed value. I noticed this while trying to make a div grow from 0 height to the content height that varies greatly due to different screen sizes(2 lines on my 2560x1440 monitor vs >10 lines on a smartphone). For this I ended up going with js.Constrictor
Very ugly solution since it creates a delay in one direction but not the other.Honorary
This is a pretty lazy solution. I'm assuming OP wants to use height : auto because the expanded height of the container is somewhat unpredictable. This solution will cause a delay before the animation becomes visible. Additionally the visible duration of the animation will be unpredictable. You'll get much more predictable (and likely smoother) results by calculating the combined height of each of the containers child nodes and then easing to an exact height value.Chelsae
For anyone interested in how to change the styles of the parent when the child get's focused: parent:focus-within is the way to go. See this answer: https://mcmap.net/q/37926/-css-change-parent-on-focus-of-childChiseler
One thing of important note here is that elements hidden this way are still visible to screen readers and can be tabbed into. For this answer, you should add visibility: hidden; and , visibility 0.15s to transition: max-height 0.15s ease-out to #menu #list and visibility: visible; to #menu:hover #list. This will ensure the list is marked as hidden when it is minimised, visible when it is shown and will delay changing it from visible to hidden until after the max-height transition has finished. See jsfiddle.net/bytesnz/onha7up5/12 for a demo.Chamaeleon
this is a good solution, just need to prepare the element like this: el.style.maxHeight = el.clientHeight+"px"; and after that, no delay :)Destructionist
Is there a "Medal of Honor" for CSS StackOverflow answers? If so, the question and this answer deserve one. I have wasted so much time tracking fixed heights to make smooth transitions. This max-height solution feels like magic. That said, I use a very fast transition to mask the animation delays between expanding and collapsing.Onstage
This is the one solution which worksAngkor
The fiddle does not work when the box has some top and bottom padding. It may be fixed, though by including the box content in a div and the padding to that child div.Pituitary
The second solution of this: keithjgrant.com/posts/2023/04/transitioning-to-height-auto works better, as it does not need a "high value", and works perfectly.Transhumance
This is a beautiful, simple answer. I find that in almost every case you can use max-height: 100vh and that reduces the delay. You can also speed up the "slide up" bit to reduce any amount of delay. Long story short, orange flags are more annoying than anything, im never quite hoSrini
Does anyone know why it is not possible to simply transition height in a traditional way in this case? I mean there must be a reason it is not added to the CSS standard, right?Oden
G
485

You should use scaleY instead.

ul {
  background-color: #eee;
  transform: scaleY(0);    
  transform-origin: top;
  transition: transform 0.26s ease;
}
p:hover ~ ul {
  transform: scaleY(1);
}
<p>Hover This</p>
<ul>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ul>

I've made a vendor prefixed version of the above code on jsfiddle, and changed your jsfiddle to use scaleY instead of height.

Edit Some people do not like how scaleY transforms the content. If that is a problem then I suggest using clip instead.

ul {
  clip: rect(auto, auto, 0, auto);
  position: absolute;
  margin: -1rem 0;
  padding: .5rem;

  color: white;

  background-color: rgba(0, 0, 0, 0.8);

  transition-property: clip;
  transition-duration: 0.5s;
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
h3:hover ~ ul,
h3:active ~ ul,
ul:hover {
  clip: rect(auto, auto, 10rem, auto);
}
<h3>Hover here</h3>
<ul>
  <li>This list</li>
  <li>is clipped.</li>
  <li>A clip transition</li>
  <li>will show it</li>
</ul>
<p>
  Some text...
</p>
Gut answered 23/6, 2013 at 10:57 Comment(9)
This method only partially achieves the desired effect but doesn't actually remove the space. The transformed box acts like a relatively positioned element - the space is taken up no matter how it is scaled. Check out this jsFiddle which takes your first one and just adds some bogus text at the bottom. Note how the text below it doesn't move up when the box height is scaled to zero.Bracteate
Now it does: jsfiddle.net/gNDX3/1 Basically you need to style your elements according to what you need. There is no silver bullet or widget like behavior in CSS/HTML.Gut
While I applaud someone trying different approaches, the real-world effect and complications this solution brings is far worse than the already awful max-height hack. Please do not use.Meadow
"Now it does" except that the content below the disappearing element jumps up before the element slides away. Could be useful sometimes, however the point of transitions is to smoothly change visuals, not trade one stark jump for another.Xeres
This method distorts the contents of the list, "squishing" them vertically, in a way that is probably not acceptable to anybody looking for an answer to this.Virgilvirgilia
@Virgilvirgilia I have added a clip transform, that does not "squish" the hidden content while animating.Gut
In effect, using a clip transform is no different than simply setting the max-height value. It still suffers from non-fixed amount of appearent animation delay with highly dynamic content size.Argyle
I see no reason why this method should not be used if the squishing effect is not a problem. This is definitely the best way keeping that aside.Heuer
The clip property its deprecated, use the current clip-path property: clip-path: inset(0% 0% 100% 0%); to hide and clip-path: inset(0% 0% 0% 0%); to show ;)Earhart
Z
284

You can't currently animate on height when one of the heights involved is auto, you have to set two explicit heights.

Zenas answered 18/8, 2010 at 12:46 Comment(0)
L
183

The solution that I've always used was to first fade out, then shrink the font-size, padding and margin values. It doesn't look the same as a wipe, but it works without a static height or max-height.

Working example:

/* final display */
#menu #list {
    margin: .5em 1em;
    padding: 1em;
}

/* hide */
#menu:not(:hover) #list {
    font-size: 0;
    margin: 0;
    opacity: 0;
    padding: 0;
    /* fade out, then shrink */
    transition: opacity .25s,
                font-size .5s .25s,
                margin .5s .25s,
                padding .5s .25s;
}

/* reveal */
#menu:hover #list {
    /* unshrink, then fade in */
    transition: font-size .25s,
                margin .25s,
                padding .25s,
                opacity .5s .25s;
}
<div id="menu">
    <b>hover me</b>
    <ul id="list">
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>

<p>Another paragraph...</p>
Lenka answered 29/5, 2015 at 14:5 Comment(3)
If you have buttons or any other non-text elements you will end up with unwanted whitespace while not hovering.Yearwood
You can further refine it by selecting all child elements with * and applying other changes. Buttons should've been affected by my above code, however, if they were correctly styled with em.Lenka
Don't do this. This is not GPU accelerated and will result in very janky animations (as demonstrated by the snippet in this answer).Catchword
R
167

This is a CSS-only solution with the following properties:

  • There is no delay at the beginning, and the transition doesn't stop early. In both directions (expanding and collapsing), if you specify a transition duration of 300ms in your CSS, then the transition takes 300ms, period.
  • It's transitioning the actual height (unlike transform: scaleY(0)), so it does the right thing if there's content after the collapsible element.
  • While (like in other solutions) there are magic numbers (like "pick a length that is higher than your box is ever going to be"), it's not fatal if your assumption ends up being wrong. The transition may not look amazing in that case, but before and after the transition, this is not a problem: In the expanded (height: auto) state, the whole content always has the correct height (unlike e.g. if you pick a max-height that turns out to be too low). And in the collapsed state, the height is zero as it should.

Demo

Here's a demo with three collapsible elements, all of different heights, that all use the same CSS. You might want to click "full page" after clicking "run snippet". Note that the JavaScript only toggles the collapsed CSS class, there's no measuring involved. (You could do this exact demo without any JavaScript at all by using a checkbox or :target). Also note that the part of the CSS that's responsible for the transition is pretty short, and the HTML only requires a single additional wrapper element.

$(function () {
  $(".toggler").click(function () {
    $(this).next().toggleClass("collapsed");
    $(this).toggleClass("toggled"); // this just rotates the expander arrow
  });
});
.collapsible-wrapper {
  display: flex;
  overflow: hidden;
}
.collapsible-wrapper:after {
  content: '';
  height: 50px;
  transition: height 0.3s linear, max-height 0s 0.3s linear;
  max-height: 0px;
}
.collapsible {
  transition: margin-bottom 0.3s cubic-bezier(0, 0, 0, 1);
  margin-bottom: 0;
  max-height: 1000000px;
}
.collapsible-wrapper.collapsed > .collapsible {
  margin-bottom: -2000px;
  transition: margin-bottom 0.3s cubic-bezier(1, 0, 1, 1),
              visibility 0s 0.3s, max-height 0s 0.3s;
  visibility: hidden;
  max-height: 0;
}
.collapsible-wrapper.collapsed:after
{
  height: 0;
  transition: height 0.3s linear;
  max-height: 50px;
}

/* END of the collapsible implementation; the stuff below
   is just styling for this demo */

#container {
  display: flex;
  align-items: flex-start;
  max-width: 1000px;
  margin: 0 auto;
}  


.menu {
  border: 1px solid #ccc;
  box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  margin: 20px;

  
}

.menu-item {
  display: block;
  background: linear-gradient(to bottom, #fff 0%,#eee 100%);
  margin: 0;
  padding: 1em;
  line-height: 1.3;
}
.collapsible .menu-item {
  border-left: 2px solid #888;
  border-right: 2px solid #888;
  background: linear-gradient(to bottom, #eee 0%,#ddd 100%);
}
.menu-item.toggler {
  background: linear-gradient(to bottom, #aaa 0%,#888 100%);
  color: white;
  cursor: pointer;
}
.menu-item.toggler:before {
  content: '';
  display: block;
  border-left: 8px solid white;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  width: 0;
  height: 0;
  float: right;
  transition: transform 0.3s ease-out;
}
.menu-item.toggler.toggled:before {
  transform: rotate(90deg);
}

body { font-family: sans-serif; font-size: 14px; }

*, *:after {
  box-sizing: border-box;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="container">
  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

  <div class="menu">
    <div class="menu-item">Something involving a holodeck</div>
    <div class="menu-item">Send an away team</div>
    <div class="menu-item toggler">Advanced solutions</div>
    <div class="collapsible-wrapper collapsed">
      <div class="collapsible">
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
        <div class="menu-item">Separate saucer</div>
        <div class="menu-item">Send an away team that includes the captain (despite Riker's protest)</div>
        <div class="menu-item">Ask Worf</div>
        <div class="menu-item">Something involving Wesley, the 19th century, and a holodeck</div>
        <div class="menu-item">Ask Q for help</div>
      </div>
    </div>
    <div class="menu-item">Sweet-talk the alien aggressor</div>
    <div class="menu-item">Re-route power from auxiliary systems</div>
  </div>

</div>

How does it work?

There are in fact two transitions involved in making this happen. One of them transitions the margin-bottom from 0px (in the expanded state) to -2000px in the collapsed state (similar to this answer). The 2000 here is the first magic number, it's based on the assumption that your box won't be higher than this (2000 pixels seems like a reasonable choice).

Using the margin-bottom transition alone by itself has two issues:

  • If you actually have a box that's higher than 2000 pixels, then a margin-bottom: -2000px won't hide everything -- there'll be visible stuff even in the collapsed case. This is a minor fix that we'll do later.
  • If the actual box is, say, 1000 pixels high, and your transition is 300ms long, then the visible transition is already over after about 150ms (or, in the opposite direction, starts 150ms late).

Fixing this second issue is where the second transition comes in, and this transition conceptually targets the wrapper's minimum height ("conceptually" because we're not actually using the min-height property for this; more on that later).

Here's an animation that shows how combining the bottom margin transition with the minimum height transition, both of equal duration, gives us a combined transition from full height to zero height that has the same duration.

animation as described above

The left bar shows how the negative bottom margin pushes the bottom upwards, reducing the visible height. The middle bar shows how the minimum height ensures that in the collapsing case, the transition doesn't end early, and in the expanding case, the transition doesn't start late. The right bar shows how the combination of the two causes the box to transition from full height to zero height in the correct amount of time.

For my demo I've settled on 50px as the upper minimum height value. This is the second magic number, and it should be lower than the box' height would ever be. 50px seems reasonable as well; it seems unlikely that you'd very often want to make an element collapsible that isn't even 50 pixels high in the first place.

As you can see in the animation, the resulting transition is continuous, but it is not differentiable -- at the moment when the minimum height is equal to the full height adjusted by the bottom margin, there is a sudden change in speed. This is very noticeable in the animation because it uses a linear timing function for both transitions, and because the whole transition is very slow. In the actual case (my demo at the top), the transition only takes 300ms, and the bottom margin transition is not linear. I've played around with a lot of different timing functions for both transitions, and the ones I ended up with felt like they worked best for the widest variety of cases.

Two problems remain to fix:

  1. the point from above, where boxes of more than 2000 pixels height aren't completely hidden in the collapsed state,
  2. and the reverse problem, where in the non-hidden case, boxes of less than 50 pixels height are too high even when the transition isn't running, because the minimum height keeps them at 50 pixels.

We solve the first problem by giving the container element a max-height: 0 in the collapsed case, with a 0s 0.3s transition. This means that it's not really a transition, but the max-height is applied with a delay; it only applies once the transition is over. For this to work correctly, we also need to pick a numerical max-height for the opposite, non-collapsed, state. But unlike in the 2000px case, where picking too large of a number affects the quality of the transition, in this case, it really doesn't matter. So we can just pick a number that is so high that we know that no height will ever come close to this. I picked a million pixels. If you feel you may need to support content of a height of more than a million pixels, then 1) I'm sorry, and 2) just add a couple of zeros.

The second problem is the reason why we're not actually using min-height for the minimum height transition. Instead, there is an ::after pseudo-element in the container with a height that transitions from 50px to zero. This has the same effect as a min-height: It won't let the container shrink below whatever height the pseudo-element currently has. But because we're using height, not min-height, we can now use max-height (once again applied with a delay) to set the pseudo-element's actual height to zero once the transition is over, ensuring that at least outside the transition, even small elements have the correct height. Because min-height is stronger than max-height, this wouldn't work if we used the container's min-height instead of the pseudo-element's height. Just like the max-height in the previous paragraph, this max-height also needs a value for the opposite end of the transition. But in this case we can just pick the 50px.

Tested in Chrome (Win, Mac, Android, iOS), Firefox (Win, Mac, Android), Edge, IE11 (except for a flexbox layout issue with my demo that I didn't bother debugging), and Safari (Mac, iOS). Speaking of flexbox, it should be possible to make this work without using any flexbox; in fact I think you could make almost everything work in IE7 – except for the fact that you won't have CSS transitions, making it a rather pointless exercise.

Ronn answered 14/5, 2017 at 14:33 Comment(5)
I wish you could shorten (simplify) your solution, or the code example at least - it looks like 90% of the code example isn't relevant to your answer.Tactic
For someone on a learning mission it is a good answer, for several reasons. First, it has complete "real life" code exxample. Someone could take that code, experiment with it, and learn more. And the detail in the explination...perfectly detailed. thank you, @Ronn , for taking the time for this answer :)Meany
Incredible ! This actually works perfectly, and with some modifications it also fits my use case of a collapsed element not being at 0 height (in my case a grid only showing one row when collapsed, and showing all rows when opened). Not only that but it seems easy to implement in a variety of contexts, can be "plugged in" existing pages without having to change too much of the parent and child elements, which a lot of other solutions can't do.Waggish
Appreciate all the thoroughness, but, being a bit of a stickler here, the animation looks and feels bad. Not a smooth timing function at the end of the day. Is that inherent to this approach?Perugia
@StevenLu To an extent, yes. I address this question in my answer, see the sentence starting with "This is very noticeable in the animation ..."Ronn
E
117

You can, with a little bit of non-semantic jiggery-pokery. My usual approach is to animate the height of an outer DIV which has a single child which is a style-less DIV used only for measuring the content height.

function growDiv() {
  var growDiv = document.getElementById('grow');
  if (growDiv.clientHeight) {
    growDiv.style.height = 0;
  } else {
    var wrapper = document.querySelector('.measuringWrapper');
    growDiv.style.height = wrapper.clientHeight + "px";
  }
}
#grow {
  -moz-transition: height .5s;
  -ms-transition: height .5s;
  -o-transition: height .5s;
  -webkit-transition: height .5s;
  transition: height .5s;
  height: 0;
  overflow: hidden;
  outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
  <div class='measuringWrapper'>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
    <div>
      The contents of my div.
    </div>
  </div>
</div>

One would like to just be able to dispense with the .measuringWrapper and just set the DIV's height to auto and have that animate, but that doesn't seem to work (the height gets set, but no animation occurs).

function growDiv() {
  var growDiv = document.getElementById('grow');
  if (growDiv.clientHeight) {
    growDiv.style.height = 0;
  } else {
    growDiv.style.height = 'auto';
  }
}
#grow {
  -moz-transition: height .5s;
  -ms-transition: height .5s;
  -o-transition: height .5s;
  -webkit-transition: height .5s;
  transition: height .5s;
  height: 0;
  overflow: hidden;
  outline: 1px solid red;
}
<input type="button" onclick="growDiv()" value="grow">
<div id='grow'>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
  <div>
    The contents of my div.
  </div>
</div>

My interpretation is that an explicit height is needed for the animation to run. You can't get an animation on height when either height (the start or end height) is auto.

Elah answered 30/6, 2010 at 13:32 Comment(4)
Since this relies on javascript, you could also easily add the measuringWrapper using javascript too!Baxy
You can do it without wrapper. Just: function growDiv() { var growDiv = document.getElementById('grow'); if (growDiv.clientHeight) { growDiv.style.height = 0; } else { growDiv.style.height = growDiv.scrollHeight+'px'; } }Mayest
I prefer this method because it is precise. I also like to adjust the folding element's transition speed based on the computed height. That way tall elements take proportionately longer to unfold than short ones. i.e. folder.style.transition = `height ${maxHeight}ms`;Desta
I think this is the best solution if you don't mind using javascript. There are a few ways with plain css but they all have some pretty big drawbacks.Venter
B
69

The accepted answer works for most cases, but it doesn't work well when your div can vary greatly in height — the animation speed is not dependent on the actual height of the content, and it can look choppy.

You can still perform the actual animation with CSS, but you need to use JavaScript to compute the height of the items, instead of trying to use auto. No jQuery is required, although you may have to modify this a bit if you want compatibility (works in the latest version of Chrome :)).

window.toggleExpand = function(element) {
    if (!element.style.height || element.style.height == '0px') { 
        element.style.height = Array.prototype.reduce.call(element.childNodes, function(p, c) {return p + (c.offsetHeight || 0);}, 0) + 'px';
    } else {
        element.style.height = '0px';
    }
}
#menu #list {
    height: 0px;
    transition: height 0.3s ease;
    background: #d5d5d5;
    overflow: hidden;
}
<div id="menu">
    <input value="Toggle list" type="button" onclick="toggleExpand(document.getElementById('list'));">
    <ul id="list">
        <!-- Works well with dynamically-sized content. -->
        <li>item</li>
        <li><div style="height: 100px; width: 100px; background: red;"></div></li>
        <li>item</li>
        <li>item</li>
        <li>item</li>
    </ul>
</div>
Brandwein answered 20/10, 2014 at 22:57 Comment(4)
@Coderer you could use clientHeightGisela
This has a very nice result. But the JavaScript looks like machine code for the most part. How could you re-write the code as though you are speaking English (readable code)?Solatium
also you can use scrollHeight when the height is 0.Inch
This is my favourite answer. But for the height calculation, I use scrollHeight as @MMD suggested, like this: element.style.height = element.scrollHeight + 'px'Lagoon
U
56

My workaround is to transition max-height to the exact content height for a nice smooth animation, then use a transitionEnd callback to set max-height to 9999px so the content can resize freely.

var content = $('#content');
content.inner = $('#content .inner'); // inner div needed to get size of content when closed

// css transition callback
content.on('transitionEnd webkitTransitionEnd transitionend oTransitionEnd msTransitionEnd', function(e){
    if(content.hasClass('open')){
        content.css('max-height', 9999); // try setting this to 'none'... I dare you!
    }
});

$('#toggle').on('click', function(e){
    content.toggleClass('open closed');
    content.contentHeight = content.outerHeight();
    
    if(content.hasClass('closed')){
        
        // disable transitions & set max-height to content height
        content.removeClass('transitions').css('max-height', content.contentHeight);
        setTimeout(function(){
            
            // enable & start transition
            content.addClass('transitions').css({
                'max-height': 0,
                'opacity': 0
            });
            
        }, 10); // 10ms timeout is the secret ingredient for disabling/enabling transitions
        // chrome only needs 1ms but FF needs ~10ms or it chokes on the first animation for some reason
        
    }else if(content.hasClass('open')){  
        
        content.contentHeight += content.inner.outerHeight(); // if closed, add inner height to content height
        content.css({
            'max-height': content.contentHeight,
            'opacity': 1
        });
        
    }
});
.transitions {
    transition: all 0.5s ease-in-out;
    -webkit-transition: all 0.5s ease-in-out;
    -moz-transition: all 0.5s ease-in-out;
}

body {
    font-family:Arial;
    line-height: 3ex;
}
code {
    display: inline-block;
    background: #fafafa;
    padding: 0 1ex;
}
#toggle {
    display:block;
    padding:10px;
    margin:10px auto;
    text-align:center;
    width:30ex;
}
#content {
    overflow:hidden;
    margin:10px;
    border:1px solid #666;
    background:#efefef;
    opacity:1;
}
#content .inner {
    padding:10px;
    overflow:auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div id="content" class="open">
    <div class="inner">
        <h3>Smooth CSS Transitions Between <code>height: 0</code> and <code>height: auto</code></h3>
        <p>A clever workaround is to use <code>max-height</code> instead of <code>height</code>, and set it to something bigger than your content. Problem is the browser uses this value to calculate transition duration. So if you set it to <code>max-height: 1000px</code> but the content is only 100px high, the animation will be 10x too fast.</p>
        <p>Another option is to measure the content height with JS and transition to that fixed value, but then you have to keep track of the content and manually resize it if it changes.</p>
        <p>This solution is a hybrid of the two - transition to the measured content height, then set it to <code>max-height: 9999px</code> after the transition for fluid content sizing.</p>
    </div>
</div>

<br />

<button id="toggle">Challenge Accepted!</button>
Unguinous answered 19/3, 2012 at 0:44 Comment(1)
This for me is ugly but the best solution for animating a height change, as all the others have compromises. However adding a subsequent animation e.g. back to the original height becomes difficult. I found if I set the height back to scrollHeight and then immediately to 0, Chrome doesn’t consistently animate it because of how quickly the properties change. Adding a timeout between the two changes worked, but the magic delay number is unknown and it adds an uncomfortable delay to the transition. Any ideas on a solution for that?Coulisse
I
54

A visual workaround to animating height using CSS3 transitions is to animate the padding instead.

You don't quite get the full wipe effect, but playing around with the transition-duration and padding values should get you close enough. If you don't want to explicitly set height/max-height, this should be what you're looking for.

div {
    height: 0;
    overflow: hidden;
    padding: 0 18px;
    -webkit-transition: all .5s ease;
       -moz-transition: all .5s ease;
            transition: all .5s ease;
}
div.animated {
    height: auto;
    padding: 24px 18px;
}

http://jsfiddle.net/catharsis/n5XfG/17/ (riffed off stephband's above jsFiddle)

Insolvable answered 26/6, 2011 at 19:5 Comment(2)
Except that here you aren't animating the height at all. You are animating the padding... it can disappear just fine because it can animate from the current state down to 0, but if you watch closely when it expands it pops open with the text and then the padding only animates.. because it doesn't know how to animate from 0 to auto.... it needs a numerical range... that's how tweening works.Malamud
I'm not sure how to put this in a way that isn't rude, but... it looks unprofessional. At least https://mcmap.net/q/37922/-how-can-i-transition-height-0-to-height-auto-using-css makes the motion "continuous" ... but it isn't smoothPerugia
A
41

According to MDN Web Docs, auto values have been intentionally excluded from the CSS transitions spec, so instead of height: auto, use height: 100%, top, or the flex property in grid and flex layouts.

Expanding/collapsing an overlay

.grid-container {
  display: grid;
  position: absolute;
}

.content {
  background: aqua;
  height: 0;
  overflow: hidden;
  transition: 1s;
}

span:hover + .grid-container .content {
  height: 100%;
}
<span>Hover over me!</span>

<div class="grid-container">

  <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

</div>

<p>Rest of the page content...</p>

Expanding/collapsing a sliding overlay

.grid-container {
  display: grid;
  position: absolute;
  overflow: hidden;
  pointer-events: none; /* to enable interaction with elements below the container */
}

.content {
  background: aqua;
  pointer-events: auto;
  position: relative;
  top: -100%;
  transition: 1s;
}

span:hover + .grid-container .content {
  top: 0;
}
<span>Hover over me!</span>

<div class="grid-container">

  <div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>

</div>

<p>Rest of the page content...</p>
Albata answered 7/11, 2021 at 9:49 Comment(11)
I was excited about the example: Expanding/collapsing in the document flow but it doesn't work as the container has a defined height so the rest of the content will be affected, the content outside the container,Incomprehensive
Yea! Thanks for shared, If I think so, in some situations where it does not matter if you overspacing at the end of the content, in the case that the content is a full page, you will always have untold spacing at the endIncomprehensive
@MarcoMesen: Just edited my example.Albata
Hey @Albata Thanks! that works great, we are basically animating the expansion of the Grid but the secret is the internal Flex, thank you very much!Incomprehensive
awesome solution, with one caveat: it seems that everything after the expanding/collapsing part needs to be in the same flex container, e.g. see jsfiddle.net/uoaxLd6w.Isoniazid
@Sandro: No big deal as the flex container is the body element: DemoAlbata
@Albata in this case it works, but it's often not easy to change the markup of the entire page, to use the body as the flex container and place the expanding/collapsing element right under it, just for this effect. Also, how would you solve two expanding elements next to each other and each element pushes down the same content beneath it?Isoniazid
Not working as document flow with grid as grid fixes the height, so the content has height growing fine but the containing grid has the fixed height, with a background or other things in flow, we saw an empty block.Heinrich
For some reason it only works for me when transitioning between height: 0 and some explicit height. I can't get height: 100% to work :(Yellowknife
@Isoniazid it is possible to use divs in place of html and body, but I don't think the issue with two or more elements next to each other can be resolved.Venter
I logged in just to say this answer is amazing! No max height and nice transitions. The key is that display: grid needs to be on an outer container...so you essentially need two containers.Beverly
S
39

Use max-height with different transition easing and delay for each state.

HTML:

<a href="#" id="trigger">Hover</a>
<ul id="toggled">
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
<ul>

CSS:

#toggled{
    max-height: 0px;
    transition: max-height .8s cubic-bezier(0, 1, 0, 1) -.1s;
}

#trigger:hover + #toggled{
    max-height: 9999px;
    transition-timing-function: cubic-bezier(0.5, 0, 1, 0); 
    transition-delay: 0s;
}

See example: http://jsfiddle.net/0hnjehjc/1/

Sanorasans answered 16/12, 2014 at 23:35 Comment(1)
The problem here is, to ensure you have enough space in a dynamic environment, you need to use ridiculous max-heights like 999999px. This, on the other hand, will shift your animation, because the frames are calculated from this value. So you might end up with animation "delay" in one direction and a really fast end in the other direction (corr: timing-functions)Kept
D
36

No hard coded values.

No JavaScript.

No approximations.

The trick is to use a hidden & duplicated div to get the browser to understand what 100% means.

This method is suitable whenever you're able to duplicate the DOM of the element you wish to animate.

.outer {
  border: dashed red 1px;
  position: relative;
}

.dummy {
  visibility: hidden;
}

.real {
  position: absolute;
  background: yellow;
  height: 0;
  transition: height 0.5s;
  overflow: hidden;
}

.outer:hover>.real {
  height: 100%;
}
Hover over the box below:
<div class="outer">
  <!-- The actual element that you'd like to animate -->
  <div class="real">
unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
content unpredictable content unpredictable content unpredictable content
  </div>
  <!-- An exact copy of the element you'd like to animate. -->
  <div class="dummy" aria-hidden="true">
unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable content unpredictable
content unpredictable content unpredictable content unpredictable content
  </div>
</div>
Damages answered 26/2, 2016 at 17:19 Comment(1)
This still takes up space. Good solution for "spoilers" but not good for collapsed content.Amethyst
B
30

Browsers have moved on a fair bit since this question was first raised. You can now transition grid track sizes and it is very well supported meaning that this problem can be solved using CSS Grid Layout

Here I am transitioning the second grid item from 0fr (0 Fractional Units) to 1fr (1 Fractional Units).

#parent0 {
  display: grid;
  grid-template-rows: min-content 0fr;
  transition: grid-template-rows 500ms;
}
#child0 {   
  background-color: #dedede;
  overflow: hidden;
}
#parent0:hover{
  grid-template-rows: min-content 1fr;
}
<div id="parent0">
  <h1>Hover me</h1>
  <div id="child0">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
  </div>
</div>
Balbinder answered 21/8, 2023 at 10:7 Comment(2)
If you prefer a video description Kevin Powell does a great job explaining why this works youtube.com/watch?v=B_n4YONte5AUnused
Nice, this is actually a really good solution for my case. Works perfectly without having to define a fixed value for the height. Thanks a lot for sharing! Wonder why this answer has not more upvotes yetLeatherneck
E
29

Update, 2024

I do not recommend my original response, but I will leave it for posterity. Ever since flexbox became widely supported, I have relied on it for solutions to issues like the one in this question.

I recommend this answer, which uses a flex container and a transition between height: 0 and height: 100% to avoid any sort of "magic values".

Original (outdated), from 2016

As I post this there are over 30 answers already, but I feel my answer improves on the already accepted answer by jake.

I was not content with the issue that arises from simply using max-height and CSS3 transitions, since as many commenters noted, you have to set your max-height value very close to the actual height or you'll get a delay. See this JSFiddle for an example of that problem.

To get around this (while still using no JavaScript), I added another HTML element that transitions the transform: translateY CSS value.

This means both max-height and translateY are used: max-height allows the element to push down elements below it, while translateY gives the "instant" effect we want. The issue with max-height still exists, but its effect is lessened. This means you can set a much larger height for your max-height value and worry about it less.

The overall benefit is that on the transition back in (the collapse), the user sees the translateY animation immediately, so it doesn't really matter how long the max-height takes.

Solution as Fiddle

body {
  font-family: sans-serif;
}

.toggle {
  position: relative;
  border: 2px solid #333;
  border-radius: 3px;
  margin: 5px;
  width: 200px;
}

.toggle-header {
  margin: 0;
  padding: 10px;
  background-color: #333;
  color: white;
  text-align: center;
  cursor: pointer;
}

.toggle-height {
  background-color: tomato;
  overflow: hidden;
  transition: max-height .6s ease;
  max-height: 0;
}

.toggle:hover .toggle-height {
  max-height: 1000px;
}

.toggle-transform {
  padding: 5px;
  color: white;
  transition: transform .4s ease;
  transform: translateY(-100%);
}

.toggle:hover .toggle-transform {
  transform: translateY(0);
}
<div class="toggle">
  <div class="toggle-header">
    Toggle!
  </div>
  <div class="toggle-height">
    <div class="toggle-transform">
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
    </div>
  </div>
</div>

<div class="toggle">
  <div class="toggle-header">
    Toggle!
  </div>
  <div class="toggle-height">
    <div class="toggle-transform">
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
      <p>Content!</p>
    </div>
  </div>
</div>
Eck answered 16/10, 2016 at 16:55 Comment(2)
"doesn't really matter" is still pretty subjective. The instant feedback from the transform's transition is certainly good, but this definitely leaves something to be desired stillPerugia
Visually looks like a pretty bad solutionAhders
P
22

No max-height, uses relative positioning, works on li elements, & is pure CSS:

I have not tested in anything but Firefox, though judging by the CSS, it should work on all browsers.

FIDDLE: http://jsfiddle.net/n5XfG/2596/

CSS

.wrap { overflow:hidden; }

.inner {
            margin-top:-100%;
    -webkit-transition:margin-top 500ms;
            transition:margin-top 500ms;
}

.inner.open { margin-top:0px; }

HTML

<div class="wrap">
    <div class="inner">Some Cool Content</div>
</div>
Paratrooper answered 1/8, 2015 at 22:35 Comment(1)
This will work until the height of the element exceeds its width. It's the basis of how margins are calculated using percentages: they are calculated based off the width of the element. So if you have a 1000 px wide element, then an element at 1100 px will be too large for this solution to work, meaning you'd have to increase that negative top margin. Basically, it's the exact same problem as using height or max-height.Yuki
V
19

A lot of answers, some better than other, most using JS. I believe I figured this out in two use-cases that are easy to understand.

Toggle an overlay

.demo01 {
  overflow: hidden;
  position: absolute;
  pointer-events: none;
}
.demo01__content {
  background: lightgray;
  padding: 1rem;
  pointer-events: all;
  transform: translateY(-100%);
  transition: transform 1s, visibility 1s;
  visibility: hidden;
}
:checked ~ .demo01 .demo01__content {
  transform: translateY(0);
  visibility: visible;
}
<input type="checkbox" /> ⬅︎ Toggle
<div>Something before 🙃</div>
<div class="demo01">
  <div class="demo01__content">
    This content should…<br />
    toggle! 👻
  </div>
</div>
<div>Something after 🙂</div>

Toggle in the document flow

.demo02 {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows 1s;
}
.demo02__content {
  align-self: end;
  min-height: 0;
  background: lightgray;
  transition: visibility 1s;
  visibility: hidden;
}
.demo02__padding {
  padding: 1rem;
}
:checked ~ .demo02 {
  grid-template-rows: 1fr;
}
:checked ~ .demo02 .demo02__content {
  visibility: visible;
}
<input type="checkbox" /> ⬅︎ Toggle
<div>Something before 🙃</div>
<div class="demo02">
  <div class="demo02__content">
    <div class="demo02__padding">
      This content should…<br />
      toggle! 👻
    </div>
  </div>
</div>
<div>Something after 🙂</div>

I wrote a blog post about these techniques.

Villous answered 1/11, 2021 at 20:0 Comment(3)
This answer could be improved by explaining the techniques here in the answer; when your blog post link dies, readers here are left to wonder what the methods are that work here that differentiate it from other answers, or what pieces of code are relevant to making it work.Bungalow
There is no transition happening, which is the core of the questionWillena
codepen.io/tacomagor/pen/RwexLjdBewail
M
16

EDIT: Scroll down for updated answer
I was making a drop down list and saw this Post ... many different answers but I decide to share my drop down list too, ... It's not perfect but at least it will using only css for drop down! I've been using transform:translateY(y) to transform the list to the view ...
You can see more in the test
http://jsfiddle.net/BVEpc/4/
I've placed div behind every li because my drop down list are coming from up and to show them properly this was needed, my div code is:

#menu div {
    transition: 0.5s 1s;
    z-index:-1;
    -webkit-transform:translateY(-100%);
    -webkit-transform-origin: top;
}

and hover is :

#menu > li:hover div {
    transition: 0.5s;
    -webkit-transform:translateY(0);
}

and because ul height is set to the content it can get over your body content that's why I did this for ul:

 #menu ul {
    transition: 0s 1.5s;
    visibility:hidden;
    overflow:hidden;
}

and hover:

#menu > li:hover ul {
     transition:none;
     visibility:visible;
}

the second time after transition is delay and it will get hidden after my drop down list has been closed animately ...
Hope later someone get benefit of this one.

EDIT: I just can't believe ppl actually using this prototype! this drop down menu is only for one sub menu and that's all!! I've updated a better one that can have two sub menu for both ltr and rtl direction with IE 8 support.
Fiddle for LTR
Fiddle for RTL
hopefully someone find this useful in future.

Magruder answered 6/10, 2013 at 10:34 Comment(0)
B
11

You can transition from height:0 to height:auto providing that you also provide min-height and max-height.

div.stretchy{
    transition: 1s linear;
}

div.stretchy.hidden{
    height: 0;
}

div.stretchy.visible{
    height: auto;
    min-height:40px;
    max-height:400px;
}
Blah answered 10/4, 2011 at 11:36 Comment(2)
This will not transition height, just max-height. height will instantly jump from 0 to auto, while max-height will transition which seems to work, but creates the same problem as other answers try to address.Socage
This is actually great idea and works when I can estimate max-height, and don't care about exact length of animation.Einkorn
E
11

Flexbox Solution

Pros:

  • simple
  • no JS
  • smooth transition

Cons:

  • element needs to be put in a fixed height flex container

The way it works is by always having flex-basis: auto on the element with content, and transitioning flex-grow and flex-shrink instead.

Edit: Improved JS Fiddle inspired by the Xbox One interface.

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  transition: 0.25s;
  font-family: monospace;
}

body {
  margin: 10px 0 0 10px;
}

.box {
  width: 150px;
  height: 150px;
  margin: 0 2px 10px 0;
  background: #2d333b;
  border: solid 10px #20262e;
  overflow: hidden;
  display: inline-flex;
  flex-direction: column;
}

.space {
  flex-basis: 100%;
  flex-grow: 1;
  flex-shrink: 0;    
}

p {
  flex-basis: auto;
  flex-grow: 0;
  flex-shrink: 1;
  background: #20262e;
  padding: 10px;
  width: 100%;
  text-align: left;
  color: white;
}

.box:hover .space {
  flex-grow: 0;
  flex-shrink: 1;
}
  
.box:hover p {
  flex-grow: 1;
  flex-shrink: 0;    
}
<div class="box">
  <div class="space"></div>
  <p>
    Super Metroid Prime Fusion
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Resident Evil 2 Remake
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Yolo The Game
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    Final Fantasy 7 Remake + All Additional DLC + Golden Tophat
  </p>
</div>
<div class="box">
  <div class="space"></div>
  <p>
    DerpVille
  </p>
</div>

JS Fiddle

Encyclopedist answered 4/8, 2017 at 21:1 Comment(3)
I like this solution. Together with absolute positioning, nested lists and pointer-events I managed to get quite a nice menu. I didn't find to many negatives. Anybody?Jughead
The CSS in my JSFiddle broke for some reason, but yeah I like this solution thanks!Encyclopedist
@Jughead just let the cursor over the top of a box and you will see...Olwen
E
11

One sentence solution: Use padding transition. It's enough for most of cases such as accordion, and even better because it's fast due to that the padding value is often not big.

If you want the animation process to be better, just raise the padding value.

.parent{ border-top: #999 1px solid;}
h1{ margin: .5rem; font-size: 1.3rem}
.children {
  height: 0;
  overflow: hidden;
  background-color: #dedede;
  transition: padding .2s ease-in-out, opacity .2s ease-in-out;
  padding: 0 .5rem;
  opacity: 0;
}
.children::before, .children::after{ content: "";display: block;}
.children::before{ margin-top: -2rem;}
.children::after{ margin-bottom: -2rem;}
.parent:hover .children {
  height: auto;
  opacity: 1;
  padding: 2.5rem .5rem;/* 0.5 + abs(-2), make sure it's less than expected min-height */
}
<div class="parent">
  <h1>Hover me</h1>
  <div class="children">Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
<div class="parent">
  <h1>Hover me(long content)</h1>
  <div class="children">Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>Some content<br>Some content
    <br>
  </div>
</div>
<div class="parent">
  <h1>Hover me(short content)</h1>
  <div class="children">Some content
    <br>Some content
    <br>Some content
    <br>
  </div>
</div>
Enciso answered 15/4, 2020 at 5:37 Comment(0)
P
10

You could do this by creating a reverse (collapse) animation with clip-path.

#child0 {
    display: none;
}
#parent0:hover #child0 {
    display: block;
    animation: height-animation;
    animation-duration: 200ms;
    animation-timing-function: linear;
    animation-fill-mode: backwards;
    animation-iteration-count: 1;
    animation-delay: 200ms;
}
@keyframes height-animation {
    0% {
        clip-path: polygon(0% 0%, 100% 0.00%, 100% 0%, 0% 0%);
    }
    100% {
        clip-path: polygon(0% 0%, 100% 0.00%, 100% 100%, 0% 100%);
    }
}
<div id="parent0">
    <h1>Hover me (height: 0)</h1>
    <div id="child0">Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>Some content
        <br>
    </div>
</div>
Palomo answered 23/2, 2020 at 14:42 Comment(1)
Great, but how to make the reveal undone with animation?Skylab
A
9

Flexible Height CSS Only Solution

I've stumbled upon a quirky solution using flex behavior. It works in at least Chrome and Firefox.

  • First, the height transition only works between 0 and 100%, two numeric values. Since "auto" is not a numeric value, fractional increments don't exist between 0 and "auto". 100% is a flexible value, so no specific height is required.

  • Second, both the outer container and the inner container of the hidden content must be set to display: flex with flex-direction: column.

  • Third, the outer container must have a height property. Setting it to 0 maintains a smooth transition only when everything is contained in the outer container because the flex behavior takes precedence over the height. Edit: Json suggested using height: fit-content, so that any content below the container is also pushed down.

.outer-container { height: 0; display: flex; flex-direction: column; }

.inner-container { display: flex; flex-direction: column; }

.hidden-content { 
    height: 0; 
    opacity: 0; 
    transition: height 1s 0.5s ease-in-out, opacity 0.5s ease-in-out;
    /* transition out: first fade out opacity, then shrink height after a delay equal to the opacity duration */
    }

.trigger:hover + .inner-container > .hidden-content { 
    height: 100%; 
    opacity: 1; 
    transition: height 1s ease-in-out, opacity 0.5s 1s ease-in-out;
    /* transition in: first expand height, then fade in opacity after a delay equal to the height duration */
}
<div class="outer-container">
  <a href="#" class="trigger">Hover to Reveal Inner Container's Hidden Content</a>
  <div class="inner-container">
    <div class="hidden-content">This is hidden content. When triggered by hover, its height transitions from 0 to 100%, which pushes other content in the same container down gradually.</div>
    <div>Within the same container, this other content is pushed down gradually as the hidden content's height transitions from 0 to 100%.</div>
  </div>
</div>

Press the Run Code Snippet button to see the transition in action. It's CSS only, with no specific height.

Azygous answered 8/1, 2022 at 1:48 Comment(4)
Instead of opacity, why not set the hidden content to use overflow: hidden. Looks good to me, however, since the outer-container has 0 height, it doesn't resize the parent height, which might not be desired. play.tailwindcss.com/aFH08CKXpUSingley
My understanding is that toggling overflow:hidden makes many transitions not work. In other projects I use javascript to toggle overflow:hidden before applying transitions (which I prefer) but for this post the goal was to use CSS only.Azygous
Setting the outer container height to "fit-content" solved it for me since I had other content below the outer container that needed to be pushed down as well.Undeviating
@Json great catch! I've updated my answer with a shout out to your improvement.Azygous
A
8

Expanding on @jake's answer, the transition will go all the way to the max height value, causing an extremely fast animation - if you set the transitions for both :hover and off you can then control the crazy speed a little bit more.

So the li:hover is when the mouse enters the state and then the transition on the non-hovered property will be the mouse leave.

Hopefully this will be of some help.

e.g:

.sidemenu li ul {
   max-height: 0px;
   -webkit-transition: all .3s ease;
   -moz-transition: all .3s ease;
   -o-transition: all .3s ease;
   -ms-transition: all .3s ease;
   transition: all .3s ease;
}
.sidemenu li:hover ul {
    max-height: 500px;
    -webkit-transition: all 1s ease;
   -moz-transition: all 1s ease;
   -o-transition: all 1s ease;
   -ms-transition: all 1s ease;
   transition: all 1s ease;
}
/* Adjust speeds to the possible height of the list */

Here's a fiddle: http://jsfiddle.net/BukwJ/

Appointor answered 9/7, 2013 at 16:14 Comment(0)
V
8

I've recently been transitioning the max-height on the li elements rather than the wrapping ul.

The reasoning is that the delay for small max-heights is far less noticeable (if at all) compared to large max-heights, and I can also set my max-height value relative to the font-size of the li rather than some arbitrary huge number by using ems or rems.

If my font size is 1rem, I'll set my max-height to something like 3rem (to accommodate wrapped text). You can see an example here:

http://codepen.io/mindfullsilence/pen/DtzjE

Update: CSS has evolved to include much more elegant techniques since I wrote this answer. I believe the best today is by using css grid and grid-template-row as described in this answer: https://mcmap.net/q/37922/-how-can-i-transition-height-0-to-height-auto-using-css

Viscounty answered 12/5, 2014 at 20:0 Comment(1)
This really is a useful solution if you need to transition a parent element containing mutliple child elements that have a set/known height, as is the case with most navigation scenarios.Sola
S
8

I understand the question asks for a solution without JavaScript. But for those interested here's my solution using just a little bit of JS.

ok, so the element's css whose height will change by default is set to height: 0; and when open height: auto;. It also has transition: height .25s ease-out;. But of course the problem is that it won't transition to or from height: auto;

So what i've done is when opening or closing set the height to the scrollHeight property of the element. This new inline style will have higher specificity and override both height: auto; and height: 0; and the transition runs.

When opening i add a transitionend event listener which will run just once then remove the inline style setting it back to height: auto; which will allow the element to resize if necessary, as in this more complex example with sub menus https://codepen.io/ninjabonsai/pen/GzYyVe

When closing i remove the inline style right after the next event loop cycle by using setTimeout with no delay. This means height: auto; is temporarily overridden which allows the transition back to height 0;

const showHideElement = (element, open) => {
  element.style.height = element.scrollHeight + 'px';
  element.classList.toggle('open', open);

  if (open) {
    element.addEventListener('transitionend', () => {
      element.style.removeProperty('height');
    }, {
      once: true
    });
  } else {
    window.setTimeout(() => {
      element.style.removeProperty('height');
    });
  }
}

const menu = document.body.querySelector('#menu');
const list = document.body.querySelector('#menu > ul')

menu.addEventListener('mouseenter', () => showHideElement(list, true));
menu.addEventListener('mouseleave', () => showHideElement(list, false));
#menu > ul {
  height: 0;
  overflow: hidden;
  background-color: #999;
  transition: height .25s ease-out;
}

#menu > ul.open {
  height: auto;
}
<div id="menu">
  <a>hover me</a>
  <ul>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
    <li>item</li>
  </ul>
</div>
Schonfeld answered 15/2, 2019 at 11:51 Comment(0)
M
8

This solution uses a few techniques:

  • padding-bottom:100% 'hack' where percentages are defined in terms of the current width of the element. More info on this technique.
  • float shrink-wrapping, (necessitating an extra div to apply the float clearing hack)
  • non-semantic use of https://caniuse.com/#feat=css-writing-mode and some transformations to undo it (this allows use of the padding hack above in a vertical context)

The upshot though is that we get performant transitioning using CSS only, and a single transition function to smoothly achieve the transition; the holy grail!

Of course, there's a downside! I can't work out how to control the width at which content gets cut off (overflow:hidden); because of the padding-bottom hack, the width and height are intimately related. There may be a way though, so will come back to it.

https://jsfiddle.net/EoghanM/n1rp3zb4/28/

body {
  padding: 1em;
}

.trigger {
  font-weight: bold;
}

/* .expander is there for float clearing purposes only */
.expander::after {
  content: '';
  display: table;
  clear: both;
}

.outer {
  float: left; /* purpose: shrink to fit content */
  border: 1px solid green;
  overflow: hidden;
}

.inner {
  transition: padding-bottom 0.3s ease-in-out;  /* or whatever crazy transition function you can come up with! */
  padding-bottom: 0%;  /* percentage padding is defined in terms of width. The width at this level is equal to the height of the content */
  height: 0;

  /* unfortunately, change of writing mode has other bad effects like orientation of cursor */
  writing-mode: vertical-rl;
  cursor: default; /* don't want the vertical-text (sideways I-beam) */
  transform: rotate(-90deg) translateX(-100%);  /* undo writing mode */
  transform-origin: 0 0;
  margin: 0;  /* left/right margins here will add to height */
}

.inner > div { white-space: nowrap; }

.expander:hover .inner,  /* to keep open when expanded */
.trigger:hover+.expander .inner {
  padding-bottom: 100%;
}
<div class="trigger">HoverMe</div>
<div class="expander">
  <div class="outer">
    <div class="inner">
      <div>First Item</div>
      <div>Content</div>
      <div>Content</div>
      <div>Content</div>
      <div>Long Content can't be wider than outer height unfortunately</div>
      <div>Last Item</div>
    </div>
  </div>
</div>
<div>
  after content</div>
</div>
Marlonmarlow answered 8/11, 2019 at 18:10 Comment(1)
As your source said "responsive square" it works only if you have a square content. So it's even not close to the accepted answer ...Extol
V
5

Set the style="" attributes for tracked elements whenever there's a change in the DOM. plugin called mutant-transition You can use CSS for your transitions and not use hacks. You don't have to write any JavaScript. Just include the JavaScript library and specify which attributes you want to watch in the HTML. You don't have to use fixed height CSS. Set what you want to track on the element in question using data-mutant-attributes="X".

<div data-mutant-attributes="height">                                                                      
    This is an example with mutant-transition                                                                                                          
</div>

This uses MutationObserver to follow changes in the DOM. You don't have to set anything up or use JavaScript to manually animate. Changes are tracked automatically. However, because it uses MutationObserver, this will only transition in IE11+. < IE11 will see snap-changes (no transition).

Fiddles

Velites answered 19/5, 2015 at 7:25 Comment(1)
Usually, when somebody asks how to do something "without JavaScript", it means the solution must work on browsers that have JavaScript disabled. It doesn't mean "without writing my own scripts". So to say that your plugin can be used without using JavaScript is misleading at best - considering it's a JavaScript plugin.Shoveler
T
5

Alternate CSS-only solution with line-height, padding, opacity and margin:

body {
  background-color: linen;
}

main {
  background-color: white;
}

[id^="toggle_"] ~ .content {
  line-height: 0;
  opacity: 0;
  padding: 0 .5rem;
  transition: .2s ease-out;
}

[id^="toggle_"] ~ .content > p {
  margin: 0;
    transition: .2s ease-out;
}

[id^="toggle_"]:checked ~ .content   {
  opacity: 1;
  padding: .5rem;
  line-height: 1.5;
}

[id^="toggle_"]:checked ~ .content p  {
    margin-bottom: .75rem;
}

[id^="toggle_"] + label {
  display: flex;
  justify-content: space-between;
  padding: 0.5em 1em;
  background: lightsteelblue;
  border-bottom: 1px solid gray;
  cursor: pointer;
}

[id^="toggle_"] + label:before {
  content: "Show";
}

[id^="toggle_"]:checked + label:before {
  content: "Hide";
}

[id^="toggle_"] + label:after {
  content: "\25BC";
}

[id^="toggle_"]:checked + label:after {
  content: "\25B2";
}
<main>
    <div>
        <input type="checkbox" id="toggle_1" hidden>
        <label for="toggle_1" hidden></label>
        <div class="content">
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dolor neque, commodo quis leo ut, auctor tincidunt mauris. Nunc fringilla tincidunt metus, non gravida lorem condimentum non. Duis ornare purus nisl, at porta arcu eleifend eget. Integer lorem ante, porta vulputate dui ut, blandit tempor tellus. Proin facilisis bibendum diam, sit amet rutrum est feugiat ut. Mauris rhoncus convallis arcu in condimentum. Donec volutpat dui eu mollis vulputate. Nunc commodo lobortis nunc at ultrices. Suspendisse in lobortis diam. Suspendisse eget vestibulum ex.
            </p>
        </div>
    </div>
    <div>
        <input type="checkbox" id="toggle_2" hidden>
        <label for="toggle_2" hidden></label>
        <div class="content">
            <p>
                Maecenas laoreet nunc sit amet nulla ultrices auctor. Vivamus sed nisi vitae nibh condimentum pulvinar eu vel lorem. Sed pretium viverra eros ut facilisis. In ut fringilla magna. Sed a tempor libero. Donec sapien libero, lacinia sed aliquet ut, imperdiet finibus tellus. Nunc tellus lectus, rhoncus in posuere quis, tempus sit amet enim. Morbi et erat ac velit fringilla dignissim. Donec commodo, est id accumsan cursus, diam dui hendrerit nisi, vel hendrerit purus dolor ut risus. Phasellus mattis egestas ipsum sed ullamcorper. In diam ligula, rhoncus vel enim et, imperdiet porta justo. Curabitur vulputate hendrerit nisl, et ultricies diam. Maecenas ac leo a diam cursus ornare nec eu quam.
            </p>
            <p>Sed non vulputate purus, sed consectetur odio. Sed non nibh fringilla, imperdiet odio nec, efficitur ex. Suspendisse ut dignissim enim. Maecenas felis augue, tempor sit amet sem fringilla, accumsan fringilla nibh. Quisque posuere lacus tortor, quis malesuada magna elementum a. Nullam id purus in ante molestie tincidunt. Morbi luctus orci eu egestas dignissim. Sed tincidunt, libero quis scelerisque bibendum, ligula nisi gravida libero, id lacinia nulla leo in elit.
            </p>
            <p>Aenean aliquam risus id consectetur sagittis. Aliquam aliquam nisl eu augue accumsan, vel maximus lorem viverra. Aliquam ipsum dolor, tempor et justo ac, fermentum mattis dui. Etiam at posuere ligula. Vestibulum tortor metus, viverra vitae mi non, laoreet iaculis purus. Praesent vel semper nibh. Curabitur a congue lacus. In et pellentesque lorem. Morbi posuere felis non diam vulputate, non vulputate ex vehicula. Vivamus ultricies, massa id sagittis consequat, sem mauris tincidunt nunc, eu vehicula augue quam ut mauris.
            </p>
        </div>
    </div>
        <div>
        <input type="checkbox" id="toggle_3" hidden>
        <label for="toggle_3" hidden></label>
        <div class="content">
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dolor neque, commodo quis leo ut, auctor tincidunt mauris. Nunc fringilla tincidunt metus, non gravida lorem condimentum non. Duis ornare purus nisl, at porta arcu eleifend eget. Integer lorem ante, porta vulputate dui ut, blandit tempor tellus. Proin facilisis bibendum diam, sit amet rutrum est feugiat ut. Mauris rhoncus convallis arcu in condimentum. Donec volutpat dui eu mollis vulputate. Nunc commodo lobortis nunc at ultrices. Suspendisse in lobortis diam. Suspendisse eget vestibulum ex.
            </p>
            <p>Sed non vulputate purus, sed consectetur odio. Sed non nibh fringilla, imperdiet odio nec, efficitur ex. Suspendisse ut dignissim enim. Maecenas felis augue, tempor sit amet sem fringilla, accumsan fringilla nibh. Quisque posuere lacus tortor, quis malesuada magna elementum a. Nullam id purus in ante molestie tincidunt. Morbi luctus orci eu egestas dignissim. Sed tincidunt, libero quis scelerisque bibendum, ligula nisi gravida libero, id lacinia nulla leo in elit.
            </p>
        </div>
    </div>
</main>
Tedda answered 28/4, 2021 at 17:38 Comment(2)
Looks nice! Doesn't care about the height. Admittedly this works best for text content. (It's mind-blowing that something so simple is so difficult to achieve. I'm appalled by the number of hacks thrown around as answers in this question.)Amethyst
BTW, I would add pointer-events: none by default and auto when checked (to the content), just to get rid of the text selection pointer and to get rid of the ability to select invisible text as well.Amethyst
S
4

I realize this thread is getting old, but it ranks high on certain Google searches so I figure it's worth updating.

You also just get/set the element's own height:

var load_height = document.getElementById('target_box').clientHeight;
document.getElementById('target_box').style.height = load_height + 'px';

You should dump this Javascript immediately after target_box's closing tag in an inline script tag.

Siloxane answered 27/5, 2011 at 1:13 Comment(0)
T
4

I have not read everything in detail but I have had this problem recently and I did what follows:

div.class{
   min-height:1%;
   max-height:200px;
   -webkit-transition: all 0.5s ease;
   -moz-transition: all 0.5s ease;
   -o-transition: all 0.5s ease;
   -webkit-transition: all 0.5s ease;
   transition: all 0.5s ease;
   overflow:hidden;
}

div.class:hover{
   min-height:100%;
   max-height:3000px;
}

This allows you to have a div that at first shows content up to 200px height and on hover it's size becomes at least as high as the whole content of the div. The Div does not become 3000px but 3000px is the limit that I am imposing. Make sure to have the transition on the non :hover, otherwise you might get some strange rendering. In this way the :hover inherits from the non :hover.

Transition does not work form px to % or to auto. You need to use same unit of measure.

Thiazole answered 6/3, 2013 at 23:13 Comment(1)
It's not working : jsfiddle.net/Imabot/ad4nj7eyPronghorn
F
4

Jake's answer to animate the max-height is great, but I found the delay caused by setting a large max-height annoying.

One could move the collapsable content into an inner div and calculate the max height by getting the height of the inner div (via JQuery it'd be the outerHeight()).

$('button').bind('click', function(e) { 
  e.preventDefault();
  w = $('#outer');
  if (w.hasClass('collapsed')) {
    w.css({ "max-height": $('#inner').outerHeight() + 'px' });
  } else {
    w.css({ "max-height": "0px" });
  }
  w.toggleClass('collapsed');
});

Here's a jsfiddle link: http://jsfiddle.net/pbatey/duZpT

Here's a jsfiddle with the absolute minimal amount of code required: http://jsfiddle.net/8ncjjxh8/

Fatness answered 17/8, 2013 at 1:46 Comment(0)
T
4

I was able to do this. I have a .child & a .parent div. The child div fits perfectly within the parent's width/height with absolute positioning. I then animate the translate property to push it's Y value down 100%. Its very smooth animation, no glitches or down sides like any other solution here.

Something like this, pseudo code

.parent{ position:relative; overflow:hidden; } 
/** shown state */
.child {
  position:absolute;top:0;:left:0;right:0;bottom:0;
  height: 100%;
  transition: transform @overlay-animation-duration ease-in-out;
  .translate(0, 0);
}

/** Animate to hidden by sliding down: */
.child.slidedown {
  .translate(0, 100%); /** Translate the element "out" the bottom of it's .scene container "mask" so its hidden */
}

You would specify a height on .parent, in px, %, or leave as auto. This div then masks out the .child div when it slides down.

Tuesday answered 13/3, 2014 at 23:19 Comment(1)
The working example of this will be much appreciated.Fenestra
D
3

Here's a solution I just used in combination with jQuery. This works for the following HTML structure:

<nav id="main-nav">
    <ul>
        <li>
            <a class="main-link" href="yourlink.html">Link</a>
            <ul>
                <li><a href="yourlink.html">Sub Link</a></li>
            </ul>
        </li>
    </ul>
</nav>

and the function:

    $('#main-nav li ul').each(function(){
        $me = $(this);

        //Count the number of li elements in this UL
        var liCount = $me.find('li').size(),
        //Multiply the liCount by the height + the margin on each li
            ulHeight = liCount * 28;

        //Store height in the data-height attribute in the UL
        $me.attr("data-height", ulHeight);
    });

You could then use a click function to set and remove the height using css()

$('#main-nav li a.main-link').click(function(){
    //Collapse all submenus back to 0
    $('#main-nav li ul').removeAttr('style');

    $(this).parent().addClass('current');

    //Set height on current submenu to it's height
    var $currentUl = $('li.current ul'),
        currentUlHeight = $currentUl.attr('data-height');
})

CSS:

#main-nav li ul { 
    height: 0;
    position: relative;
    overflow: hidden;
    opacity: 0; 
    filter: alpha(opacity=0); 
    -ms-filter: "alpha(opacity=0)";
    -khtml-opacity: 0; 
    -moz-opacity: 0;
    -webkit-transition: all .6s ease-in-out;
    -moz-transition: all .6s ease-in-out;
    -o-transition: all .6s ease-in-out;
    -ms-transition: all .6s ease-in-out;
    transition: all .6s ease-in-out;
}

#main-nav li.current ul {
    opacity: 1.0; 
    filter: alpha(opacity=100); 
    -ms-filter: "alpha(opacity=100)";
    -khtml-opacity: 1.0; 
    -moz-opacity: 1.0;
}

.ie #main-nav li.current ul { height: auto !important }

#main-nav li { height: 25px; display: block; margin-bottom: 3px }
Dorcas answered 2/11, 2011 at 23:58 Comment(0)
S
3

To transition from any starting height, including 0, to auto (full size and flexible) without requiring hard-set code on a per-node basis or any user-code to initialize: https://github.com/csuwildcat/transition-auto.

What you want: http://codepen.io/csuwldcat/pen/kwsdF

Slap the following JS file into your page, and after that add/remove a single boolean attribute--reveal=""--from the nodes you want to expand and contract.

Do as the user, once you include the code block found below the example code:

/*** Nothing out of the ordinary in your styles ***/

<style>
    div {
        height: 0;
        overflow: hidden;
        transition: height 1s;
    }
</style>

/*** Just add and remove one attribute and transition to/from auto! ***/

<div>
    I have tons of content and I am 0px in height you can't see me...
</div>

<div reveal>
     I have tons of content and I am 0px in height you can't see me...
     but now that you added the 'reveal' attribute, 
     I magically transitioned to full height!...
</div>

Drop this JS file in your page:

/*** Code for height: auto; transitioning ***/

(function(doc){

/* feature detection for browsers that report different values for scrollHeight when an element's overflow is hidden vs visible (Firefox, IE) */
var test = doc.documentElement.appendChild(doc.createElement('x-reveal-test'));
    test.innerHTML = '-';
    test.style.cssText = 'display: block !important; height: 0px !important; padding: 0px !important; font-size: 0px !important; border-width: 0px !important; line-height: 1px !important; overflow: hidden !important;';
var scroll = test.scrollHeight || 2;
doc.documentElement.removeChild(test);

var loading = true,
    numReg = /^([0-9]*\.?[0-9]*)(.*)/,
    skipFrame = function(fn){
      requestAnimationFrame(function(){
        requestAnimationFrame(fn);
      });
    },
    /* 2 out of 3 uses of this function are purely to work around Chrome's catastrophically busted implementation of auto value CSS transitioning */
    revealFrame = function(el, state, height){
        el.setAttribute('reveal-transition', 'frame');
        el.style.height = height;
        skipFrame(function(){
            el.setAttribute('reveal-transition', state);
            el.style.height = '';
        });
    },
    transitionend = function(e){
      var node = e.target;
      if (node.hasAttribute('reveal')) {
        if (node.getAttribute('reveal-transition') == 'running') revealFrame(node, 'complete', '');
      } 
      else {
        node.removeAttribute('reveal-transition');
        node.style.height = '';
      }
    },
    animationstart = function(e){
      var node = e.target,
          name = e.animationName;   
      if (name == 'reveal' || name == 'unreveal') {
        
        if (loading) return revealFrame(node, 'complete', 'auto');
        
        var style = getComputedStyle(node),
            offset = (Number(style.paddingTop.match(numReg)[1])) +
                     (Number(style.paddingBottom.match(numReg)[1])) +
                     (Number(style.borderTopWidth.match(numReg)[1])) +
                     (Number(style.borderBottomWidth.match(numReg)[1]));
                     
        if (name == 'reveal'){
          node.setAttribute('reveal-transition', 'running');
          node.style.height = node.scrollHeight - (offset / scroll) + 'px';
        }
        else {
            if (node.getAttribute('reveal-transition') == 'running') node.style.height = '';
            else revealFrame(node, 'running', node.scrollHeight - offset + 'px');
        }
      }
    };

doc.addEventListener('animationstart', animationstart, false);
doc.addEventListener('MSAnimationStart', animationstart, false);
doc.addEventListener('webkitAnimationStart', animationstart, false);
doc.addEventListener('transitionend', transitionend, false);
doc.addEventListener('MSTransitionEnd', transitionend, false);
doc.addEventListener('webkitTransitionEnd', transitionend, false);

/*
    Batshit readyState/DOMContentLoaded code to dance around Webkit/Chrome animation auto-run weirdness on initial page load.
    If they fixed their code, you could just check for if(doc.readyState != 'complete') in animationstart's if(loading) check
*/
if (document.readyState == 'complete') {
    skipFrame(function(){
        loading = false;
    });
}
else document.addEventListener('DOMContentLoaded', function(e){
    skipFrame(function(){
        loading = false;
    });
}, false);

/* Styles that allow for 'reveal' attribute triggers */
var styles = doc.createElement('style'),
    t = 'transition: none; ',
    au = 'animation: reveal 0.001s; ',
    ar = 'animation: unreveal 0.001s; ',
    clip = ' { from { opacity: 0; } to { opacity: 1; } }',
    r = 'keyframes reveal' + clip,
    u = 'keyframes unreveal' + clip;

styles.textContent = '[reveal] { -ms-'+ au + '-webkit-'+ au +'-moz-'+ au + au +'}' +
    '[reveal-transition="frame"] { -ms-' + t + '-webkit-' + t + '-moz-' + t + t + 'height: auto; }' +
    '[reveal-transition="complete"] { height: auto; }' +
    '[reveal-transition]:not([reveal]) { -webkit-'+ ar +'-moz-'+ ar + ar +'}' +
    '@-ms-' + r + '@-webkit-' + r + '@-moz-' + r + r +
    '@-ms-' + u +'@-webkit-' + u + '@-moz-' + u + u;

doc.querySelector('head').appendChild(styles);

})(document);

/*** Code for DEMO ***/

    document.addEventListener('click', function(e){
      if (e.target.nodeName == 'BUTTON') {
        var next = e.target.nextElementSibling;
        next.hasAttribute('reveal') ? next.removeAttribute('reveal') : next.setAttribute('reveal', '');
      }
    }, false);
Spiritualist answered 9/10, 2013 at 22:35 Comment(1)
I want to upvote this, but instead of answering the question of how to make it work, you shared a plugin that you wrote to make it work. We, the curious, are left to reverse engineer your plugin, which isn't much fun. I wish you would update your answer to contain more explanation of what your plugin does and why. Change the code to be more explanatory. For example, you have a whole section of code which just writes out static CSS. I'd rather see the CSS, than the code that generates it. You can leave out the boring parts, like repeating for all browser prefixes.Immixture
S
3

The max-height solution from Jake works well, if the hard-coded max-height value supplied is not much bigger than the real height (because otherwise there are undesirable delays and timing problems). On the other hand if the hard-coded value accidentially is not bigger than the real height the element won't open up completely.

The following CSS only solution also requires a hard-coded size that should be bigger than most of the occurring real sizes. However this solution also works if the real size is in some situations bigger than the hard-coded size. In that event the transition might jump a bit, but it will never leave a partially visible element. So this solution could also be used for unknown content, e.g. from a database, where you just know that the content is usually not bigger than x pixels, but there are exceptions.

Idea is to use a negative value for margin-bottom (or margin-top for a slightly diffenrent animation) and to place the content element into a middle element with overflow:hidden. The negative margin of the content element so reduces the height of the middle element.

The following code uses a transition on margin-bottom from -150px to 0px. This alone works fine as long as the content element is not higher than 150px. In addition it uses a transition on max-height for the middle element from 0px to 100%. This finally hides the middle element if the content element is higher than 150px. For max-height the transition is just used to delay its application by a second when closing, not for a smooth visiual effect ( and therefore it can run from 0px to 100%).

CSS:

.content {
  transition: margin-bottom 1s ease-in;
  margin-bottom: -150px;
}
.outer:hover .middle .content {
  transition: margin-bottom 1s ease-out;
  margin-bottom: 0px
}
.middle {
  overflow: hidden;
  transition: max-height .1s ease 1s;
  max-height: 0px
}
.outer:hover .middle {
  transition: max-height .1s ease 0s;
  max-height: 100%
}

HTML:

<div class="outer">
  <div class="middle">
    <div class="content">
      Sample Text
      <br> Sample Text
      <br> Sample Text
      <div style="height:150px">Sample Test of height 150px</div>
      Sample Text
    </div>
  </div>
  Hover Here
</div>

The value for margin bottom should be negative and as close as possible to the real height of the content element. If it('s absoute value) is bigger there are similar delay and timing problems as with the max-height solutions, which however can be limited as long as the hard coded size is not much bigger than the real one. If the absolute value for margin-bottom is smaller than the real height the tansition jumps a bit. In any case after the transition the content element is either fully displayed or fully removed.

For more details see my blog post http://www.taccgl.org/blog/css_transition_display.html#combined_height

Soerabaja answered 8/8, 2014 at 9:24 Comment(0)
W
3

I liked the solution of jake and dotnetCarpenter. But jake's solution lacked the control on transition and dotnetCarpenter's solution does not remove the space. So I kinda fused those two solution which works fine for me. This is what I got:

---------------------- css class based solution ------------------------

HTML:

<div>
    <div id="title">
        Show items
    </div>
    <ul class="theul hideul">
        <li>one</li>
        <li>two</li>
        <li>three</li>
    </ul>
</div>

CSS:

ul{list-style: none;}
.hide {
    max-height: 0;
    transform: scale(1, 0);
    transform-origin: top;
    transition: transform 0.3s ease-in, max-height 0.3s ease-in;
    > li {
        transform: scale(1, 0);
        transition: transform 0.3s ease-in;
    }
}
.show {
    max-height: 999px;
    transform: scale(1, 1);
    transform-origin: top;
    transition: transform 0.3s ease-in, max-height 0.3s ease-in;
    > li {
        transform: scale(1, 1);
        transition: transform 0.3s ease-in;
    }
} 

Add event listener on #title to toggle between hide and show class.

---------------------- only css solution ------------------------

if you dont' want any of javascript, then this is only css solution(same solution):

HTML:

<div>
    <div id="title">
        Show items
    </div>
    <ul>
        <li>one</li>
        <li>two</li>
        <li>three</li>
    </ul>
</div>

CSS:

ul {
    list-style: none;
    max-height: 0;
    transform: scale(1, 0);
    transform-origin: top;
    transition: transform 0.3s ease-out, max-height 0.3s ease-in-out;
    /* transition-delay: 0.1s; */
    > li {
        transform: scale(1, 0);
        transition: transform 0.3s ease-in;
    }
}
#title2:hover + ul {
    max-height: 999px;
    transform: scale(1, 1);
    transform-origin: top;
    transition: transform 0.3s ease-in, max-height 0.3s ease-in-out;
    /* transition-delay: 0.1s; */
    > li {
        transform: scale(1, 1);
        transition: transform 0.3s ease-in;
    }
}

Sidenote: If you dont care about transition, then details is one of the best way to write dropdown ui. HTML

<details>
        <summary>
            <p>show items</p>
        </summary>
        <ul>
            <li>item 1</li>
            <li>item 2</li>
            <li>item 3</li>
        </ul>
    </details>

if you want to add a custom dropdown icon, then remove the default icon first:

summary::marker {
        content: none;
    }
Waxy answered 30/8, 2023 at 11:55 Comment(0)
S
3

Yet another solution to this issue would be using grid template. You can use as starting point of transition

display: grid;
grid-template-rows: 0fr;

as the finishing for your transition add grid-template-rows: 1fr;

and in the wrapper for content you should add overflow: hidden. Here is my own code in scss:

.accordion-content {
    opacity: 0;
    transition: opacity 0.3s ease, grid-template-rows 0.3s ease;
    display: grid;
    grid-template-rows: 0fr;

        .section-content {
            overflow: hidden;
        }

        &.show {
            opacity: 1;
            grid-template-rows: 1fr;
        }
    }
Soggy answered 28/9, 2023 at 7:36 Comment(0)
W
2

This isn't exactly a "solution" to the problem, but more of a workaround. It only works as written with text, but can be changed to work with other elements as needed I'm sure.

.originalContent {
    font-size:0px;
    transition:font-size .2s ease-in-out;
}
.show { /* class to add to content */
    font-size:14px;
}

Here is an example: http://codepen.io/overthemike/pen/wzjRKa

Essentially, you set the font-size to 0 and transition that instead of the height, or max-height, or scaleY() etc. at a quick enough pace to get the height to transform to what you want. To transform the actual height with CSS to auto isn't currently possible, but transforming the content within is, hence the font-size transition.

  • Note - there IS javascript in the codepen, but it's only purpose is to add/remove css classes on click for the accordion. This can be done with hidden radio buttons, but I wasn't focused on that, just the height transformation.
Wescott answered 12/10, 2016 at 0:12 Comment(1)
Effectively duplicates this answer, but omits the opacity-fade effect.Estren
S
2

There seems to be no proper solution. max-height approach is quite good but doesn't work well for the hide phase - there will be a noticeable delay unless you know the height of the content.

I think the best way is to use max-height but only for the show phase. And not to use any animation on hiding. For most cases it shouldn't be crucial.

max-height should be set to a quite huge value to ensure any content fits. Animation speed can be controlled using transition duration (speed = max-height / duration). Speed does not depend on the size of the content. The time it takes to show the whole content will depend on its size.

document.querySelector("button").addEventListener(
  "click", 
  function(){
    document.querySelector("div").classList.toggle("hide");
  }
)
div {    
    max-height: 20000px;
    transition: max-height 3000ms;
    overflow-y: hidden;
}

.hide {
    max-height: 0;
    transition: none;
}
<button>Toggle</button>
<div class="hide">Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. Lorem ipsum dolor sit amet, ius solet dignissim honestatis ad. Mea quem tibique intellegat te. Insolens deterruisset cum ea. Te omnes percipit consulatu eos. Vix novum primis salutatus no, eam denique sensibus et, his ipsum senserit ne. 
</div>
Strachan answered 24/3, 2017 at 19:38 Comment(0)
R
1

I just animated the <li> element instead of the whole container:

<style>
.menu {
    border: solid;
}
.menu ul li {
    height: 0px;
    transition: height 0.3s;
    overflow: hidden;
}
button:hover ~ .wrapper .menu ul li,
button:focus ~ .wrapper .menu ul li,
.menu:hover ul li {
    height: 20px;
}
</style>


<button>Button</button>
<div class="wrapper">
    <div class="menu">
        <ul>
            <li>menuitem</li>
            <li>menuitem</li>
            <li>menuitem</li>
            <li>menuitem</li>
            <li>menuitem</li>
            <li>menuitem</li>
        </ul>
    </div>
</div>

you can add ul: margin 0; to have 0 height.

Reynaud answered 1/4, 2020 at 16:52 Comment(1)
This again only works when the height is known.Figwort
J
1

I combined both max-height and negative margin to achive this animation.

I used max-height: 2000px, but you can push that number to much higher value if needed. I animate the max-height on the expand and the margin on collapse.

The js part is just the click, can be replaced with :hover or checkbox for pure css solution.

There are only 2 problems i can see so far,

  1. The transition-timing is limited. (i added only 2 timings)
  2. If you click again while the dropdown is collapsing, it will jump.

Here's the result

[...document.querySelectorAll('.ab')].forEach(wrapper => {
    wrapper.addEventListener('click', function () {
        this.classList.toggle('active');
    });
});
* {
  margin: 0;
  box-sizing: border-box;
}

.c {
  overflow: hidden;
}

.items {
  width: 100%;
  visibility: hidden;
  max-height: 0;
  margin-bottom: -2000px;
  -webkit-transition: margin 0.6s cubic-bezier(1, 0, 1, 1), max-height 0s 0.6s linear, visibility 0s 0.6s linear;
  transition: margin 0.6s cubic-bezier(1, 0, 1, 1), max-height 0s 0.6s linear, visibility 0s 0.6s linear;
}
.items > * {
  padding: 1rem;
  background-color: #ddd;
  -webkit-transition: background-color 0.6s ease;
  transition: background-color 0.6s ease;
}
.items > *:hover {
  background-color: #eee;
}

.ab {
  padding: 1rem;
  cursor: pointer;
  background: #eee;
}
.ab.active + .c .items {
  max-height: 2000px;
  margin-bottom: 0;
  visibility: visible;
  -webkit-transition: max-height 0.6s cubic-bezier(1, 0, 1, 1);
  transition: max-height 0.6s cubic-bezier(1, 0, 1, 1);
}

.dropdown {
  margin-right: 1rem;
}

.wrapper {
  display: -webkit-box;
  display: flex;
}
<div class="wrapper">
    <div class="dropdown">
        <div class="ab">just text</div>
        <div class="ab">just text</div>
        <div class="ab">dropdown</div>
        <div class="c">
            <div class="items">
                <p>items</p>
                <p>items</p>
                <p>items asl;dk l;kasl;d sa;lk</p>
                <p>items sal;kd</p>
                <p>items</p>
            </div>
        </div>
        <div class="ab">just text</div>
        <div class="ab">just text</div>
    </div>
    
    <div class="dropdown">
        <div class="ab">dropdown</div>
        <div class="c">
            <div class="items">
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
                <p>items</p>
            </div>
        </div>
        <div class="ab">text</div>
    </div>
    
    <div class="dropdown">
        <div class="ab">placeholder</div>
        <div class="ab">dropdown</div>
        <div class="c">
            <div class="items">
                <p>items</p>
                <p>items</p>
            </div>
        </div>
        <div class="ab">placeholder</div>
        <div class="ab">placeholder</div>
        <div class="ab">placeholder</div>
    </div>
</div>
<h1>text to be pushed</h1>
Jarmon answered 7/9, 2020 at 20:54 Comment(0)
I
1

I want to add an example about how to expanding/collapsing keeping the doc flow, this example is for React apps using tailwindcss

export function Collapse({ collapsed = true, children }) {
  return (
    <div className="grid">
      <div className="flex flex-col">
        <div className={`transition-all duration-500 overflow-hidden ${collapsed ? 'basis-0' : 'flex-1'}`}>
          {children}
        </div>
      </div>
    </div>
  );
}

To go deep read: https://mcmap.net/q/37922/-how-can-i-transition-height-0-to-height-auto-using-css

Incomprehensive answered 6/1, 2022 at 2:17 Comment(1)
I gotta say, I'm not a huge fan of this sort of animation, though. I like when the toggle button slides down smoothly and the inner content reveals itself once the animation is complete.Semeiology
H
0

The approach I use is to have everything based on font-size (using em as unit) or at least everything that affects the vertical size of the box we want to animate open. Then if anything isn't in em units (for example a 1px border) I set that to 0 when the box is closed, by targetting all children with *.

What the animation animates is the font-size in % from 0 to 100. Of what? Of a known font-size of the parent.

The rules for it to work are that everything in the animated box must:

  • use % for all font-sizes and line-height
  • use em for everything vertical (like height, margin and padding top/bottom, etc)
  • if using px for something vertical (e.g. border top/bottom) it should be overwritten when the box is closed (e.g. border-top:0;border-bottom:0;)

In a way you can still use pixels as reference unit, by simply setting the wrapper font-size to 100px e.g. #page{font-size:100px;} so if you want 10px anywhere inside you can use 0.1em.

This is not the prettiest thing anyone can write, but hey, these browsers don't give us any beautiful solution to this problem. As soon as the height of the box is unpredictable we have no choice but to get somewhat dirty, and this is the least dirty thing I came up with.

Hover version:

https://jsfiddle.net/xpsfkb07/1/

#page {
  font-size: calc( (35vw + 65vh) / 30); /* just some responsive design as a bonus */
}
#slidebox {
  background-color: #e8e8e8;
  visibility: hidden;
  font-size: 0; /* animated from 0 to 100% */
  opacity: 0; /* optional */
  transition: 0.5s;
}
a#ahover:hover ~ #slidebox {
  visibility: visible;
  font-size: 100%; /* animated from 0 to 100% */
  opacity: 1; /* optional */
}
a#ahover:not(:hover) ~ #slidebox * {
  border-top: 0;
  border-bottom: 0;
  /* Put here anything vertical that uses px as unit, in this case the borders */
}
a#button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 20em;
  height: 3em;
  padding: 0.5em;
  border: 2px solid #0080ff;
  border-radius: 0.4em;
  background-color: #8fbfef;
  color: #404040;
  box-sizing: border-box;
}
#someform {
  margin-top: 1em;
  margin-bottom: 1em;
  padding: 1em 4em 1em 4em;
  background-color: #d8ffd8;
  border: 1px solid #888888;
}
#someform input {
  display: inline-block;
  box-sizing: border-box;
  font-size: 125%;
  padding: 0.5em;
  width: 50%; /* anything horizontal can still be in px or % */
  border-radius: 0.4em;
  border: 1px solid red;
}
<div id=page>
  <a id=ahover href="#">Hover me</a><br>
  Here is the box that slides:
  <div id=slidebox>
    I am the content of the slide box (line1).<br>
    I am the content of the slide box (line2).<br>
    <a id=button href="#">I am some button in the slide box</a><br>
    I am the content of the slide box (line3).<br>
    I am the content of the slide box (line4).
    <div id=someform>
      Some box with a form or anything...<br>
      <input type=text value="Text Box">
    </div>
    I am the content of the slide box (line5).<br>
    I am the content of the slide box (line6).
  </div>
  And this is after the box.
</div>

Class change version:

https://jsfiddle.net/8xzsrfh6/

const switch_ele = document.getElementById('aclass');
switch_ele.addEventListener('click', function(){
    const box_ele = document.getElementById('slidebox');
    box_ele.className = box_ele.className == 'show' ? 'hide' : 'show';
}, true);
#page {
  font-size: calc( (35vw + 65vh) / 30); /* just some responsive design as a bonus */
}
#slidebox {
  background-color: #e8e8e8;
  visibility: hidden;
  font-size: 0; /* animated from 0 to 100% */
  opacity: 0; /* optional */
  transition: .5s;
}
#slidebox.show {
  visibility: visible;
  font-size: 100%; /* animated from 0 to 100% */
  opacity: 1; /* optional */
}
#slidebox.hide * {
  border-top: 0;
  border-bottom: 0;
  /* Put here anything vertical that uses px as unit, in this case the borders */
}
a#button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: 20em;
  height: 3em;
  padding: 0.5em;
  border: 2px solid #0080ff;
  border-radius: 0.4em;
  background-color: #8fbfef;
  color: #404040;
  box-sizing: border-box;
}
#someform {
  margin-top: 1em;
  margin-bottom: 1em;
  padding: 1em 4em 1em 4em;
  background-color: #d8ffd8;
  border: 1px solid #888888;
}
#someform input {
  display: inline-block;
  box-sizing: border-box;
  font-size: 125%;
  padding: 0.5em;
  width: 50%; /* anything horizontal can still be in px or % */
  border-radius: 0.4em;
  border: 1px solid red;
}
<div id=page>
  <a id=aclass href="#">Switch class w/ js</a><br>
  Here is the box that slides:
  <div id=slidebox class=hide>
    I am the content of the slide box (line1).<br>
    I am the content of the slide box (line2).<br>
    <a id=button href="#">I am some button in the slide box</a><br>
    I am the content of the slide box (line3).<br>
    I am the content of the slide box (line4).
    <div id=someform>
      Some box with a form or anything...<br>
      <input type=text value="Text Box">
    </div>
    I am the content of the slide box (line5).<br>
    I am the content of the slide box (line6).
  </div>
  And this is after the box.
</div>
Hadrian answered 8/10, 2021 at 6:23 Comment(0)
F
-1

Not having found any good solution with CSS, I made this with jQuery

$(document).ready(function() {
  $('.nav_menu .menu-item-has-children').each(function(index) {
    var smheight = Math.ceil($(this).find(".sub-menu").outerHeight());
    $(this).find(".sub-menu").height(0);
    $(this).mouseenter(function() {
      $(this).find(".sub-menu").animate({
        height: smheight + "px"
      }, 250);
    });
  });
  $('.nav_menu .menu-item-has-children').each(function(index) {
    $(this).mouseleave(function() {
      $(this).find(".sub-menu").animate({
        height: "0"
      }, 250);
    });
  });
});
.menu-item-has-children {
  background-color: violet;
}
li ul {
  overflow: hidden;
  background-color: lime;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="nav_menu">
  <li class="menu-item-has-children">
    <a href="#">Main Thing</a>
    <ul class="sub-menu">
      <li><a href="#">Sub Thing</a></li>
      <li><a href="#">Sub Thing</a></li>
      <li><a href="#">Sub Thing</a></li>
    </ul>
  </li>
  <li class="menu-item-has-children">
    <a href="#">Main Thing</a>
    <ul class="sub-menu">
      <li><a href="#">Sub Thing</a></li>
      <li><a href="#">Sub Thing</a></li>
      <li><a href="#">Sub Thing</a></li>
    </ul>
  </li>
</ul>
Figwort answered 21/9, 2023 at 10:19 Comment(0)
B
-2

I wanted to share the method I ended up using (and haven't seen yet) Let me start by saying it is not possible to translate to auto without javascript:

It CAN work with CSS transitions but requires some JavaScript to change auto the `scrollheight

The way it works:

  • The container has overflow: hidden.
  • set the height of the element to the scrollHeight.
  • To collapse, set the height to 0px in the next animation frame
  • toggle() toggles the collapsed class just to track the state

This way the CSS transition will work because it is made from 0px to scrollHeight instead of auto.

function collapse(el, collapse) {
    el.style.height = el.scrollHeight+'px';
    if(!collapse)
        requestAnimationFrame(() => el.style.height = '0px');
}
     
function toggle(btn) {
    const ul = btn.nextElementSibling,
      collapsed = ul.classList.contains('collapsed');
      
    collapse(ul, collapsed);
    
    ul.classList.toggle('collapsed');
}
ul {
    transition: height 0.16s ease-out;
    overflow:hidden;
    height:auto;
}
<ul>
  <li>Item 1
  <li>Item 2
  <li>Item 3
</ul>
<button onclick="toggle(this)">Toggle</button>
<ul>
  <li>Item 4
  <li>Item 5
  <li>Item 6
</ul>
<ul>
  <li>Item 7
  <li>Item 8
  <li>Item 9
</ul>
Boliviano answered 3/8, 2023 at 15:26 Comment(2)
Please read the question title: The OP is looking for a CSS solution.Albata
You are right and my answer is using CSS. OP asked: "using CSS transitions" so at least I answered the main question. I've added 3 links to show CSS transitions on height:auto will not work. thereby answering the "Without JavaScript" partBoliviano
S
-4

Set the height to auto and transition the max-height.

Tested on Chrome v17

div {
  position: absolute;
  width:100%;
  bottom:0px;
  left:0px;

  background:#333;
  color: #FFF;

  max-height:100%; /**/
  height:auto; /**/

  -webkit-transition: all 0.2s ease-in-out;
  -moz-transition: all 0.2s ease-in-out;
  -o-transition: all 0.2s ease-in-out;
  -ms-transition: all 0.2s ease-in-out;
  transition: all 0.2s ease-in-out;
}

.close {
  max-height:0%; /**/
}
Sherburne answered 17/3, 2012 at 4:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.