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>
I have not read everything in detail but I have had this problem recently and I did what follows:
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. This works fine for me. Using HTML5 makes it perfect....
Remember that there is always a work around... ; )
Hope someone finds this useful
I know this is the thirty-somethingth answer to this question, but I think it's worth it, so here goes. This is a CSS-only solution with the following properties:
transform: scaleY(0)
), so it does the right thing if there's content after the collapsible element.height: auto
) state, the whole content always has the correct height (unlike e.g. if you pick amax-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.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: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.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.
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:
We solve the first problem by giving the container element a
max-height: 0
in the collapsed case, with a0s 0.3s
transition. This means that it's not really a transition, but themax-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 numericalmax-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 aheight
that transitions from 50px to zero. This has the same effect as amin-height
: It won't let the container shrink below whatever height the pseudo-element currently has. But because we're usingheight
, notmin-height
, we can now usemax-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. Becausemin-height
is stronger thanmax-height
, this wouldn't work if we used the container'smin-height
instead of the pseudo-element'sheight
. Just like themax-height
in the previous paragraph, thismax-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.
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.
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.
There was little mention of the
Element.scrollHeight
property which can be useful here and still may be used with a pure CSS transition. The property always contains the "full" height of an element, regardless of whether and how its content overflows as a result of collapsed height (e.g.height: 0
).As such, for a
height: 0
(effectively fully collapsed) element, its "normal" or "full" height is still readily available through itsscrollHeight
value (invariably a pixel length).For such an element, assuming it already has the transition set up like e.g. (using
ul
as per original question):We can trigger desired animated "expansion" of height, using CSS only, with something like the following (here assuming
ul
variable refers to the list):That's it. If you need to collapse the list, either of the two following statements will do:
My particular use case revolved around animating lists of unknown and often considerable lengths, so I was not comfortable settling on an arbitrary "large enough"
height
ormax-height
specification and risking cut-off content or content that you suddenly need to scroll (ifoverflow: auto
, for example). Additionally, the easing and timing is broken withmax-height
-based solutions, because the used height may reach its maximum value a lot sooner than it would take formax-height
to reach9999px
. And as screen resolutions grow, pixel lengths like9999px
leave a bad taste in my mouth. This particular solution solves the problem in an elegant manner, in my opinion.Finally, here is hoping that future revisions of CSS address authors' need to do these kind of things even more elegantly -- revisit the notion of "computed" vs "used" and "resolved" values, and consider whether transitions should apply to computed values, including transitions with
width
andheight
(which currently get a bit of a special treatment).Set the height to auto and transition the max-height.
Tested on Chrome v17
I think I came up with a really solid solution
OK! I know this problem is as old as the internet but I think I have a solution which I turned into a plugin called mutant-transition. My solution sets the
style=""
attributes for tracked elements whenever theres a change in the DOM. the end result is that you can use good ole CSS for your transitions and not use hacky fixes or special javascript. The only thing you have to do is set what you want to track on the element in question usingdata-mutant-attributes="X"
.Thats it! This solution uses MutationObserver to follow changes in the DOM. Because of this, you don't really have to set anything up or use javascript to manually animate things. Changes are tracked automatically. However, because it uses MutationObserver, this will only transition in IE11+.
Fiddles!
height: auto
toheight: 100%
height: auto
when adding children