This optimized compositing process is where all the individual textures of a website are uploaded to the GPU then drawn out to create the final picture we see on our screen; it all happens behind the scenes before a single frame of content is presented.
In this article, we’re going to focus on the rendering performance of animations and transitions, as they can be one of the biggest offenders in performance bottlenecks.
How Animations and Transitions Affect Performance
When we animate an element, the browser has to re-evaluate the page — frame by frame — to determine if and how the element has affected the flow of nearby elements and the overall page, then recalculate the positions and geometries of those elements. This process is called reflow.
The browser also looks for areas that need to be re-repainted due to changes in an element’s color or background properties. Every time a repaint or reflow happens, the browser has to composite those areas all over again. These reflows and repaints can be “expensive,” making our animations and transitions appear janky.
The key to great animation performance is keeping the number of areas being reflowed and repainted as low as possible. But how do we know if the browser is doing more repainting and reflowing than it needs to?
Testing Animations and Transitions
It’s always important to test the performance of an animation or transition in order to see exactly how much work the browser is doing. Using the Timeline panel and rendering settings of Chrome DevTools, we’ll test four transition/animation methods to determine which one will always give us the smoothest results.
The goal is to keep the browser’s paint times as low as possible and frame rates at or near 60 frames per second (FPS).
Note: To learn more about Chrome DevTools, watch our course on Website Optimization.
Top/Left Margin Transition
We’ll start with a test that transitions the top and left margins of the logo:
Results: The average paint time is somewhere around 19ms, but the frame rates are dropping down to nearly 30fps at times, which usually results in janky transitions. Animating or transitioning properties like margin and padding cause reflows since they’re animated on the browser’s main thread, so it’s best to avoid animating or transitioning margins.
Top/Left Offset Animation
Next, we’ll position the logo absolute, then animate its top and left position with a keyframe animation sequence.
Results: The paint times are higher, averaging 23ms, and the frame rates are still much lower than 60fps.
It’s also worth pointing out that absolutely-positioned elements will not cause reflows on other elements. They’re separated from the normal document flow, which minimizes the reflow calculations done by the browsers.
Transform, with Translate3D
Let’s see what happens when we transition the logo using the
translate3d CSS transform function.
Results: Once the transition starts, the paint times are at 0.0ms and the frame rates are steadily above 30fps — that’s exactly what we want! Notice the outline around the logo at 00:13. That indicates it’s being composited on a separate layer by the GPU, making paint times and frame rates faster.
When an element is animated or transitioned on a separate GPU layer, everything on that layer moves as one big block of pixels, so the browser doesn’t need to calculate every single pixel on every frame.
Other CSS properties that trigger that type of acceleration are
What About jQuery Animations?
Many jQuery effects, like the
.animate() method, are also commonly used to animate elements. Let’s see what happens when we run a similar animation using jQuery.
Results: The paint time average is ~20ms — on par with the “main thread” CSS transition and animation. But the frame rates are really low, dropping below 30fps at times — notice the spikes in FPS (big yellow bars) once the hover event is fired and the animation starts.
Although we don’t have complete control over CSS animations and transitions, the main benefit to using them is that the browser can optimize them by compositing them on a separate layer. However, forcing too many compositing layers with
translate3d() may also result in poor rendering performance.
For example, giving every child element in an animation
transform: translateZ(0), when adding it to the parent element is all that’s necessary. Too many layers force longer composite times, which leads to choppy animations.
Animating or transitioning properties like margin, padding, border, position, font-size — even width and height will affect layout and cause reflows. And properties like visibility, border-style, border-radius and background will affect painting.
So it’s always best to animate or transition the two properties that affect compositing:
opacity. Doing so prevents the browser from having to do a repaint or reflow on every frame and, because the site’s textures are already on the GPU, the browser does less work to recomposite and draw pixels to the screen.