Performance
There are two primary factors that contribute to the performance of a web animation: rendering, and hardware acceleration.
Rendering is the process of taking an update to the DOM and reflecting that change on screen. This affects the performance of all animations.
Hardware acceleration is the ability to run an animation off the main JavaScript thread. This affects the performance of animations that run at the same time as other JS processes, like React rendering or other data crunching.
Let's take a deeper look at each.
Rendering
When we update the styles an element:
The browser needs to reflect this change by re-rendering the page, taking the HTML and CSS and turning it into an image that shows on your screen.
Generally speaking, the steps a browser's renderer takes to do this are:
Layout: Calculate the size and position of elements on the page
Paint: Draw the page into graphical layers - essentially individual images that make up the page
Composite: Draw these layers to the viewport.
As a rule of thumb, it is quicker to do less work.
Slow rendering
For instance, if we update one element's height
, this changes the size of the element. Changing the size of an element might then affect the size and/or position of a sibling or child element, which itself might have knock-on effects, and so on. So we need to recalculate the layout of all the affected elements.
Whenever the layout of a page changes, the browser also needs to repaint and recomposite the affected layers too. Many styles affect layout, like height
, border-width
, padding
, position
, etc.
Smooth animations usually run at 60 frames a second (fps), the same as most screen refresh rates. So if we want to change the height
of an element at 60fps, we need to be able to re-render in 16.7 milliseconds. Screens and browsers are increasingly capable of animating at 120fps, so this time window can be reduced further to 8ms.
Re-renders can easily take upwards of 100ms! This is why animating layout is so often discouraged.
However, this isn't a hard and fast rule. If an element's layout is isolated, for instance it has position: absolute
and very few children, then you might be able to animate it smoothly. Just make sure you test these animations on low-powered devices.
Fast rendering
The best performing styles are those that trigger only the third rendering step, the compositor.
In every modern browser, transform
and opacity
will operate directly on a layer. These are therefore the safest values to animate across all devices.
Browsers are adding more values to this list, too. For instance, Chrome and Firefox handle filter
and SVGs entirely on the compositor, and Chrome is adding support for background-color
and clip-path
soon.
Additionally, because Motion One is built on the Web Animations API, browsers are smart enough to automatically place elements animating these values on a new graphical layer.
Everything in-between
There are also plenty of values like box-shadow
and border-radius
that can be updated without triggering a render, but do require a potentially expensive paint (step 2).
You should always test the animation of these properties on low-powered devices.
However, there are still ways you can improve the performance of these animations.
Reduce layer size
First, the time a browser takes to repaint a layer is proportional to its size. So you can improve the performance of these animations by making smaller layers.
You can hint to the browser that they should create a new layer by setting the willChange
style to "transform"
:
Creating new layers doesn't come for free. Each takes space on the GPU. So do so sparingly.
Use alternative styles
Second, you can try replacing styles will better-performing alternatives.
We've already seen that browsers are improving support for filter
on the compositor. So instead of animating boxShadow
, animate filter
with the drop-shadow
function:
Likewise, browsers are adding clipPath
to the compositor. So instead of animating borderRadius
, animate clipPath
with the inset
function:
Which is which?
So how do you tell which styles will trigger layout or paint, and which will just trigger composition?
Most tutorials on this subject will recommend CSS Triggers. While this is still a good guide to catching layout-triggering styles (border
, width
, top
), it's very out of date, missing data on clip-path
, filter
and outdated info on others.
Browsers are changing all the time so the best approach is when venturing outside transform
and opacity
to test cross-browser and cross-device. Every browser has performance profiling tools, most can remotely debug mobile devices, so be sure to use those to investigate further.
Hardware acceleration
Rendering performance should be your primary focus because it will be a consideration in every animation you make. But there's the additional factor of the animation process itself and whether it can be hardware accelerated.
An animation, at its most basic, mixes two values over a duration of time. For example, if we wanted to animate between "100px"
and "200px"
over one second, if 0.5
seconds has elapsed our animation would calculate "150px"
. This is a very simple calculation and doesn't produce noticeable overhead compared to rendering.
However, in a browser there are several ways we can compute this value:
With JavaScript, using
requestAnimationFrame
(like GSAP and Framer Motion)With the Web Animations API (like Motion One and Framer Motion)
With CSS
The JavaScript code will always run on the main JS thread. This means if your app is running other JS code at the same time, your animation code could be blocked from running at all. This will result in janky animations.
However, WAAPI and CSS can run some animations off the main JS thread, directly on the compositor itself. As this is usually a GPU, this is often referred to as hardware acceleration.
An animation that is hardware accelerated will remain smooth, no matter how busy your main JS thread becomes.
Because Motion One is built on WAAPI, it can also run hardware accelerated animations.
Accelerated values
As a general rule, if a style invokes only the composite rendering step, it can theoretically be animated on it, too.
transform
and opacity
are widely supported compositor styles and these do usually animate on the compositor.
Other values like filter
, background-color
, clip-path
and SVGs either have support or are gaining it in most browsers.
CSS variables and individual transforms
Motion One is unique from WAAPI and CSS in that it supports the animation of individual transforms:
Under the hood, it is animating CSS variables, and currently these are not accelerated, even though they're being applied to transform
.
So if hardware acceleration is crucial in your use-case, stick to animating transform
:
Other exceptions
Even when animating supposedly performant styles, each browser has different rules for when an animation does or doesn't receive acceleration.
For instance, until very recently, if Chrome detected a %
based transform like this:
It wouldn't be hardware accelerated.
Webkit's exceptions
Webkit has quite a number of bail-outs from accelerated animations.
This seems to be because it attempts to leverage Core Animation, and as a result many of the bugs in that then surface in Webkit.
Additionally, Core Animation doesn't have a full feature overlap with the WAAPI spec.
The short term fix usually employed by the Webkit team is to detect the conditions that lead to the bugs or where the specs are mismatched and then disable hardware acceleration. Leading to a patchwork of conditions you should be aware of.
They are:
If
playbackRate
is set to a value other than1
:If
direction
is set to a value other than"normal"
:If
step
easing is used:If
cubic-bezier
easing is used with ay
value outside of the0-1
range (commonly used for overshoot), AND there are more than two keyframes or two keyframes that don't uselinear
easing:Finally,
filter
is supported, but only in macOS.
There are also a myriad of timing bugs like synchronisation issues between the main thread and compositor.
For this reason, Motion One actually disables hardware acceleration by default in Webkit.
We'll be monitoring the state of accelerated animations in Webkit and hopefully can remove this limitation in the future. Until then, it is possible to allow Webkit to run accelerated animations with the allowWebkitAcceleration
option:
However, we recommend thoroughly testing these animations in Safari and an embedded WKWebView
browser like the one used in iOS Chrome.
Progressive enhancement
All of this is to say, it can be a minefield ensuring your animations are hardware accelerated.
We recommend treating acceleration like progressive enhancement. It's great when a browser supports it, but usually not essential.
If you do encounter a lot of jank from a busy JavaScript thread, stick to transform
and opacity
, set allowWebkitAcceleration
to true
, and ensure you test thoroughly in Webkit browsers.
Conclusion
To achieve smooth animations your main priority should be which values you animate. As a developer, that is the thing you have most control over.
transform
and opacity
are cheapest to render in all browsers. Browsers are improving the rendering performance of other styles (and SVG) all the time, with filter
, background-color
and clip-path
on the horizon.
Layout-triggering styles can be animated on elements that don't affect the layout of surrounding elements (for instance if they're position: absolute
). But make sure you test these animations in many browsers, especially low-powered devices.
Finally, hardware acceleration is an excellent tool to avoid jank if your website is performing heavy processing during an animation. But it isn't something you have full control over, there are many conditions under which it gets disabled.