You’ve probably used CSS Animations or CSS Transitions in a project. (If not, check out CSS-Trick’s almanac entries on animations and transitions.) Some of your animations might have performed smoothly. Other might have appeared choppy. Do you wonder why?
In this article, we’ll explore how browsers handle CSS Animations and CSS Transitions, so you can develop intuition around whether an animation is likely to perform well — before writing any code! With this intuition, you’ll be able to make design decisions that play well with the browser and result in silky smooth user experiences.
Let’s pop the hood of the browser, and look around. Once we understand how it works, we can drive it better.
Modern browsers typically have two important threads of execution. These threads work together to render a web page:
Typically, the main thread is responsible for:
Typically, the compositor thread is responsible for:
The main thread can be busy for long periods of time running your JavaScript or painting a large element. While it’s busy, it’s not responsive to user input.
On the other hand, the compositor thread tries to stay extremely responsive to user input. The compositor tries to redraw the page 60 times per second when the page is changing, even if the page is incomplete.
For example, when the user scrolls a page, the compositor thread asks the main thread to update the bitmaps for newly visible parts of the page. However, if the main thread doesn’t respond quickly enough, the compositor doesn’t wait. The compositor draws the parts of the page it has so far and draws white elsewhere.
I mentioned the compositor thread draws bitmaps to the screen using the GPU. Let’s quickly go over the GPU.
The GPU is a chip found in most phones, tablets, and computers today. It’s extremely specialized, meaning it’s really good at certain things, and it’s not that great at others.
GPUs are really fast at:
GPUs are relatively slow at:
Now that we have a rough idea of the software and hardware running our page, let’s look at how the browser’s main thread and compositor thread work together to perform a CSS Transition.
Suppose we’re transitioning an element’s height from 100px to 200px, like so:
div { height: 100px; transition: height 1s linear; } div:hover { height: 200px; }
The main thread and the compositor thread will perform operations according to the timeline diagram below. Note that operations in orange boxes are potentially time-consuming. Operations in blue boxes are quick.
As you can see, there are lots of orange boxes, meaning the browser has to work pretty hard! This means the transition might be choppy.
In every frame of the transition, the browser has to perform layout, painting, and uploading new bitmaps to the GPU. As we learned, loading bitmaps into GPU memory can be a relatively slow operation.
The reason the browser has to work so hard every frame is because the contents of the element keep changing. Changing an element’s height may cause its child elements to also change in size, so the browser has to perform layout. After layout, the main thread has to regenerate the bitmap for the element.
So, height can be somewhat expensive to transition. Is there something cheaper?
Suppose we’re scaling an element from half size to full size. Also suppose we’re using the CSS transform property to scale it and the CSS transition property to animate the scaling, like so:
div { transform: scale(0.5); transition: transform 1s linear; } div:hover { transform: scale(1.0); }
Let’s look at the timeline diagram for this case:
We see a lot less orange this time, meaning the animation will probably be smooth! So, how is animating an element’s transform different than animating its height?
By definition, the CSS transform property does not change the layout of an element or the elements around it. It affects the element as a whole- it scales the whole element or rotates the whole element or moves the whole element.
This is great news for the browser! The browser only has to generate the bitmap for the element and upload it to the GPU at the start of the animation. After that, the browser doesn’t have to do any more layout, painting, or bitmap uploading. Instead, the browser can leverage the GPU’s special ability to draw the same bitmap in a different position, rotation, or scale quickly.
So, does this mean we shouldn’t animate an element’s height? No. Sometimes it’s exactly what your design warrants, and the animation could be fast enough. Maybe your element is isolated, and doesn’t cause other parts of the page to be laid out again. Maybe your element is simple to repaint, and the browser can do it quickly. Maybe your element is small, and the browser only has to upload a small bitmap to the GPU.
Of course, if you can animate a “cheaper” property like CSS transform instead of a more expensive property like CSS height, and there is no impact on your design vision, do that. For example, lets say your design involves a button that reveals a menu when tapped. Instead of animating the menu’s CSS top or height properties to reveal it, try animating the element’s CSS transform property for a similar or identical effect.
The CSS properties that are particularly fast to animate include:
This list is somewhat limited today, but as browsers advance, you’ll see more and more CSS properties becoming fast to animate. Also, don’t discount the current list. You might be surprised at just how many rich effects you can create by combining these properties. Get creative!