The 60 FPS target

For web animations to look smooth, browsers need to hit a frame rate of around 60 frames per second. A frame represents all the work required to update and repaint the screen; that work must finish within roughly 16.7ms (1000ms / 60) or users will perceive a delay.

Modern browsers can animate only two CSS properties cheaply at that rate: transform and opacity. Animating anything else means you likely won't reach a silky-smooth 60 FPS. Understanding why requires looking at how browsers turn your code into pixels.

The rendering pipeline

To display a page, the browser follows four sequential steps:

  1. Style: Calculate the styles that apply to the elements.
  2. Layout: Generate the geometry and position for each element.
  3. Paint: Fill out the pixels for each element.
  4. Composite: Separate the elements into layers and draw the layers to the screen.

When you animate something on a loaded page, these steps must run again, starting from the step that the animation requires to change. Because the steps are sequential, animating something that changes layout forces paint and composite to run as well. The further down the pipeline an animation starts, the cheaper it is.

Layout property costs

Layout changes involve recalculating the geometry of all affected elements. Changing one element's size or position can cascade: alter the width of the <html> element, for instance, and its children may need new geometry. Due to overflow and inter-element effects, changes low in the tree can even drive layout calculations all the way back to the top. The larger the tree of visible elements, the longer layout takes.

Paint property costs

Paint determines the order in which elements are drawn to the screen. It is often the longest-running task in the pipeline. Most painting in modern browsers is done in software rasterizers, and depending on how elements are grouped into layers, other elements besides the changed one may also need repainting.

Composite property benefits

Compositing separates the page into layers, converts layer information into pixels via rasterization, and then puts the layers together to create the final page. That is why opacity is cheap to animate: if the property sits in its own layer, changes can be handled by the GPU during compositing. Chromium-based browsers and WebKit automatically create a new layer for any element with a CSS transition or animation on opacity.

How browser layers work

Placing animated or transitioned elements on their own layer means the browser only needs to repaint those items, not the whole page. This is conceptually similar to layers in photo-editing software. Browsers generally make sound decisions about which elements warrant a new layer, but you can force layer creation if needed — though this should be done with caution.

Each layer consumes memory, so on devices with constrained memory, creating new layers can introduce more problems than it solves. Additional layers also require texture uploads to the GPU, which may hit bandwidth limits between the CPU and GPU.

CSS versus JavaScript animation

CSS-based animations and Web Animations are usually handled on the compositor thread, which is separate from the main thread where styling, layout, painting, and JavaScript execute. If the main thread is busy with an expensive task, compositor-thread animations can continue without interruption. In many cases, changes to transform and opacity can also be handled on the compositor thread.

However, any animation that triggers paint or layout forces the main thread to do work. That holds for both CSS and JavaScript animations; the overhead of layout or paint will generally dwarf the cost of CSS or JavaScript execution, making the choice of technology moot.