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
Despite its rampant use, you should generally avoid CSS transitions on the
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
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
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.
transition-timingapplies to the
max-heightrange, not the
height. This adds unintended delays and completely changes your timing curve. Basically it's nearly impossible to get the effect you want.
Method 3: Calculate
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
Method 4: Nuke from orbit with FLIP + Inverse Scaling
Remember Method 1 and all the problems that using
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
linearfor simplicity. It only gets more complicated if you want to use different timings.
- This is even more coupled than the