How To Do CSS Transitions With Height: Auto
Creating a smooth expand/collapse animation seems easy. Surely you can set a transition on height: auto
, and it'll just work?
Sadly, the CSS gods aren't so kind.
Instead of the smooth open and close you envisaged, your element suddenly reappears at its new height. No smooth animation. Not what you pictured at all.
As it turns out, animating on CSS auto
values doesn't work, and it's honestly a real shame. The only way the CSS animation will work is if we know the size of our elements in advance - a luxury we rarely have.
The bad news is that there isn't a universal best approach, but there are still several options at our disposal.
Method 1: Use transform
Despite its rampant use, you should generally avoid CSS transitions on the height
or width
properties (among some others).
These properties affect the page layout. To see how other elements on the page are being affected during your animation, the browser completely recalculates your page's layout.
For every. single. frame.
This is where the CSS transform
property comes in. transform
causes your element to be animated like an image, and avoids all the layout recalculations. If you can use this approach, you should.
Unfortunately there are two deal-breakers in using transform
to collapse your content:
- The content warps when it collapses.
- It leaves a gap where the content was.
Method 2: Animate max-height
/ max-width
Instead of animating on height
, a different approach is to set a fixed (and large) max-height
and then transition that to 0px.
The upside of this approach is that it's straightforward. It also manages to avoid the weird warping effect of transform: scale
.
There are some downsides, though:
- We're animating on
height
, with all its layout-thrashing goodness. - You need a hardcoded value for
max-height
, so you at least need some idea of how large your element will be. - Your
transition-timing
applies to themax-height
range, not theheight
. This adds unintended delays and completely changes your timing curve. Basically it's nearly impossible to get the effect you want.
Method 3: Calculate height
with JavaScript
A lengthier (but more precise) approach is to use JavaScript to calculate the animation start and end using the element's rendered height
.
Once we determine the element's actual height, we add it to the element's inline styles to give the animation something to transition between.
In terms of effect, this is probably the one you were going for. Here are the downsides:
- We're still animating on
height
, so performance is going to be bad. - We've introduced a fair chunk of code to address a very narrow problem
- It introduces tight coupling between your JavaScript and CSS, which is prone to breaking in the future.
Method 4: Nuke from orbit with FLIP + Inverse Scaling
Remember Method 1 and all the problems that using transform
caused?
Well, it turns out you can fix them. To address content warping, you can apply scaling in the other direction. You can also use the FLIP technique to move surrounding content in a performant way.
The scaling works by calculating our start and end sizes and interpolating the scaling values into two animations that cancel out each other's scaling effects.
We also calculate the end position of the sibling elements and apply a transition to move them there while the scaling animation is happening.
This looks much better than our initial transform
attempt, and solves the problems we were having. However:
- This solution is big and complex. We've introduced a lot of code now, and it's still a very narrow problem.
- Several moving parts need to stay synchronised, making it very prone to bugs and breakage.
- We have to provide a custom easing function through JS. I've used
linear
for simplicity. It only gets more complicated if you want to use different timings. - This is even more coupled than the
height
example.