Sam Thorogood

How I Learnt To Stop Worrying And Love Animating The Box Model

As someone who likes to animate things on the web, my general rule of thumb has always been:

Doing these two things keeps your animations 'cheap', because your browser doesn't have to redraw anything during the animation. But it's incredibly limiting.

This post is just some simple thoughts on animating a humble box as an accordion. This just means an element which open and closes vertically with an animation.

I'm going to cover some nuances on this, but the short version of this post is: for a few seconds, it's probably fine, even if you're animating the height (or friends) property to do so. 🪗

The Demo

First, a sweet demo!

This demo isn't available in RSS, sorry!

⚠️ If you open Chrome's DevTools and enable Paint flashing before toggling the demo, note that this paragraph doesn't flash: we've given it its own layer via will-change: transform. Other browsers may vary.

This demo combines a bunch of concepts that I'll talk about below. Read on 👇

The Basics

You can build accordions in a bunch of ways. The legacy way is to animate an outer element's height or max-height while also setting overflow: hidden.

  1. Animate max-height between zero (hidden) and something ridiculous (like 100vh). This is fine, but has odd timing issues: the animation still has to run between your element's actual height and the large value, but there's no visible changes here.

  2. Determine the contained element's actual height with JS (via .offsetHeight or similar), so you can animate the accordion's height to that exact value. However, if the page's width changes, an open accordion can look incorrect as its content isn't measured again.

You can solve the second approach's issue by using ResizeObserver, and measuring the correct height of the accordion again. Although this fix is really uncommon out there in the real world—try resizing other pages with open accordions—the demo above does this.

CSS Grid (Update)

(January 2023) There's a novel new way to shrink and expand an accordion with CSS Grid. Check it out đź‘€

An Extension

The demos in this article actually use a hack with negative margin-top to shrink their size.

One parent containing two child elements; the second simply sets a negative `margin-top` to shrink the parent element
The red element just pushes the other one up, constraining the parent element's size

It actually goes further: for the third red element, it sets its width to the height of the black content element. We then animate margin-top from 0% through -100%, as a margin specified in percent always reflects the width of an element—even when applied to the top or bottom margin.

Confused? Well, me too, but this has a bunch of advantages I'd happily tell you all about on Twitter or if you ever get me in the room with a whiteboard. 🤔🖌️

Er, otherwise, read on ⬇️

Animation Implications

Any element after an accordion has to redraw constantly during an animation. The elements are fundamentally being repositioned on the parent element (eventually flowing through to <body>), so the browser has to do work.

It's possible to create solo layers that get moved around without being redrawn—like the paragraph immediately following the demo—but this is needless extra layer cost for the vast majority of your time the page isn't moving around.

Ideal World

In the ideal world, we'd "chunk" long-running pages into distinct parts: a static section, an accordion section, a static section, and so on. These sections could be wrapped in layers as needed, so that one section expanding or collapsing doesn't effect another. (Many accordion libraries out there in the real world do just this but only for the elements they control, neglecting the page below them.)

Here's a demo where you can play around with these concepts. By toggling the checkboxes on the left, you can force each individual section to have a layer; you can toggle the accordion parts with the larger button.

Sorry, this demo is only available at the site!

If you're on a browser which supports the CSS Painting API, the last section actually has a white box which will update every time it is rendered. Try putting it into a layer and toggling the accordion above it—the paint example won't render as it moves.

Implementation

The demo is fine and all, but what should you do with this information? Well…

Ideally, a page would actually turn any content following an accordion into a layer just for the duration of an animation—it'll draw once at the start and end, but not at 60fps (or 120fps on say, a modern iPad) during.

This might be infeasible, depending on the way your page is laid out, or it might involve many layers to deal with the tree structure of HTML.

But…

Having said that—how long is your animation? If this is opening and closing in say, .5 seconds, that's 30-60 frames at most, and it's only when a user is actively interacting with your content anyway. Can you let the user's CPU run for that time? 🤔

Chrome uses multiple threads to rasterize content, which means your painting is pretty fast. (We see this via the CSS Painting API, but it's visible in DevTools for everything else too.) But this argument could also be made to keep rendering all the time—yeah, rendering is great on modern machines with umpteen cores. Not so much on your 2015-era low-end Moto G3's…

Alternative

An alternative for accordions that choose between multiple things—think, a FAQ section or something—is to give the illusion of shifting choice while all being contained in a fixed size box. That outer box could, if you wanted to get tricky, actually be the size of the greatest contained element.

Done

That's all! I just wanted to write about accordions, and I hope it's been enlightening. Please @-me if you want to say hi or let me know how you accordion!