Performant expand & collapse animations

I recently stumbled upon this tutorial on how to build performant expand & collapse animations, which consists in using scale transforms when animating clips, and prevent the skewing of the content by counter-scaling it…magic!

The css property overflow: hidden on the container will do the clipping, and the counter-scale transform on the content will avoid the skew effect. For a vertical expand animation, all we have to do is animate the container from transform: scaleY(0) to transform: scaleY(1), and counter-scale the content from transform: scaleY(∞) to transform: scaleY(1).

The nice thing of this approach is that is content-agnostic: the content can change even during the animation, and we’d still have a buttery-smooth transition. Moreover our animation is hardware accelerated as we only modify transform!

If we were to use the keyframes interpolation, we’d have incorrect counter-scale values: e.g. let’s animate the container scaleY from 0.1 to 1 with a linear animation timing function: the content scaleY will go from 10 to 1. At 50% of the animation, container’s scaleY will be 0.1+(1–0.1)*0.5 = 0.55, while content’s transform will be 10-(10–1)*0.5 = 5.5, instead of 1/0.55 = 1.818. This will result in an accordion animation rather than vertical expand.

an example of the accordion effect —,output

This technique requires the generation of the animation keyframes, as we need to compute the counter-scale for each keyframe in order to avoid the skew effect on the content. We need to generate enough keyframes for a 60fps animation, and use a step animation timing function to avoid interpolation (aka the accordion effect).

I made a custom element, animation-keyframes, to do exactly this: generate the animation keyframes for you. There is also an online animation keyframes editor to create and customize your animation. Here some animations you can generate:

I’ve used animation-keyframes to build two other custom-elements, <dropdown-menu> and <expand-collapse>. The difference between the two is that <dropdown-menu> overlaps the content on top of the trigger when opened, while <expand-collapse> reveals additional content around an element.

For <dropdown-menu> all we need is the vertical expand animation that animates from transform: scaleY(0) to transform: scaleY(1) when opened, and from opacity: 1 to opacity: 0 when closed. The cost is an external css file that weights ~1.2kB gzipped.

For <expand-collapse>, we need to compute the scale ratios according to the size of the trigger element in order to properly clip the content. The cost is generating the animation keyframes for each expand-collapse instance, which is ~2ms on a MacBookPro (~23ms with a 10x CPU slowdown).

In conclusion, these experiments show that we have few choices to make when using this technique: is it cheaper/faster to download static assets or to generate them on the fly?

Dynamicity comes with a price, it’s up to us to chose how much of it we can afford, and how to distribute it.

Both <dropdown-menu> and <expand-collapse> choose to generate 1 second worth of keyframes in order to have good fps up to 2 seconds animation duration. Just reducing the duration in half has an immediate benefit on the file size which is cut in half as well — see the stylesheet.

Let me know what is the best configuration that works for you!

Share your fancy animation keyframes in the comments 👇