Animations can help create more welcoming and engaging user experiences. They can improve user perception and memory of your brand. However, adding quality animations is not always an easy win. We can’t just sprinkle animations here and there willy-nilly!
Let’s imagine your website is an elevator pitch: you have a short time to win users over, and people won’t wait around if there is a delay. Your website needs to be fast, so sluggish animations are a no-no.
Today, I will discuss how you can create high-performance animations.
A new study commissioned by Google investigated the impact of mobile website speeds on businesses. The findings show how intimately linked speed and user experience (UX) are.
Speed has a direct impact on user experience. Speed plays a vital role in the success of any digital initiative, and is pronounced on e-commerce and other transactional sites. 70% of consumers admit that page speed impacts their willingness to buy from an online retailer and in the US, latency is the number one reason why consumers decide to abandon mobile sites, with 10% blaming slow downloads as a reason for not purchasing.
- Milliseconds make Millions Report by Deloitte and 55.
The study found that a 100ms improvement in site speed led to retail consumers spending almost 10% more. Also, luxury consumers engaged more, with page views increasing by 8%.
The stakes are high!
Site speed is also a huge factor for search engines ranking your website, even though the companies behind search engines are not forthcoming with how important a factor speed is exactly.
People don’t access your website in the same ways. Devices vary. Internet speeds vary. Browsing habits vary. The same website is experienced differently depending on the user’s situation. If you want everyone to have a good experience on your website, you need to design for lower speed connections and older devices.
The Web Almanac conducts an annual state of the web report. The results on performance in the 2020 report show that there is a lot of work to do to meet the goals set for fast websites, but there is some cause for optimism.
It’s important we don’t overlook or forget these facts along the way.
Google has a user-centric performance model called RAIL. RAIL stands for the four distinct aspects of the web app life cycle: response, animation, idle, and load.
RAIL sets specific performance goals and provides guidelines on how to achieve them.
The performance goals are:
Let’s look into the animation performance goals in more detail. Technically, the maximum budget for each frame is 16ms (1000ms / 60 frames per second≈16ms), but browsers need about 6ms to render each frame, hence the guideline of 10ms per frame. Why 60 frames per second?
If we create a new frame every time the screen is refreshed, we will have smooth movement. The typical device refresh rate is 60Hz (hertz), which means the screen refreshes 60 times per seconds.
But to run at 60fps at different refresh rates, we need a frame to be rendered in a small amount of time:
This is a difficult standard to meet when you factor in the variance in the devices. It is not just about the refresh rate of the screen, but obviously the speed of the device. On the slowest device, 60fps is likely to be out of reach.
The number one priority is to avoid jank — anything that lags or creates jerky movement. What we are striving for is to be efficient and to keep the frame rate as high as possible.
Another vital metric to strive for is a consistent frame-rate. Fluctuations will be seen by the user as stuttering. Google introduced a new metric recently called Average Dropped Frame (ADF). ADF is a smoothness metric that measures the GPU and rendering performance of a webpage. The lower the ADF, the smoother the page will be. This is the other key metric we should monitor.
For user interactions, a response time within 100 milliseconds feels like the result is immediate. This should be the target for triggering an animation. To ensure a visible response is given within 100ms, user input events should be processed within 50ms.
If the response time is longer than 100ms, a slight delay is perceived by user. If this is the case, you should provide feedback to the user such as a progress bar.
You should consider how an animation fits in with the overall performance of the webpage. Setting a performance budget is very helpful for design-related decision-making. You can create a performance budget in the Lighthouse tool with a simple JSON file with your acceptable values for the vital page metrics. If your animation is inactive or off-screen when the page loads, your animation will have far less impact on these metrics.
There is a general algorithm that the major browsers follow to render a frame. Google call it the pixel pipeline; whereas Mozilla call it the rendering waterfall.
Each of the steps in the algorithm triggers those that come after:
The browser doesn’t have to go through each step every time. If a change only requires a paint to be performed, then it will skip the layout step.
As you can probably guess, if the browser takes fewer steps to render a frame, the performance will be better. If we understand what types of changes trigger the different steps, then we can make better decisions when we create animations.
Google created a small website called CSS Triggers, which lists the CSS properties with the rendering steps they trigger. It hasn’t been updated in a few years now, so it’s accuracy is questionable. You can look at it to get a rough idea.
First of all, it’s important to say that we don’t want to make rules. Rules imply that if you follow them, you will get the right outcome every time. It doesn’t work that way! The context of what you’re doing matters, of course. And things change over time as browser internals change.
It is better to have a set of design heuristics to help you make sound design decisions. You shouldn’t be intimidated by them — they are there to help you avoid pitfalls. You should use DevTools to measure performance along the way to validate what you’re doing.
I’m not advocating for doing your animations in CSS, JavaScript, or using a JavaScript library. You can achieve similar performance in any of these. There is plenty of nuance and context to that statement, but it is a different conversation that we won’t get into here.
Let’s cover our design heuristics then.
transform
and opacity
If we use properties that only affect the composition step of the rendering algorithm, we will get the best performance. The transform
and opacity
properties fall into this category.
The rendering for these properties can be offloaded to the GPU, but the caveat is that the element should be on its own layer for that to happen. Elements are promoted to a layer by the browser automatically in some cases, but you can trigger it yourself also.
For example, this Star Wars animation effectively has 3 layers.
How do you promote an element to a layer?
will-change
. For older browsers that don’t support will-change
, you can add transform: translateZ(0);
to an element as a hacktranslate3d()
and matrix3d()
will create a layer. You may notice that some JavaScript libraries will do this under the hoodYou need to be selective in what elements you promote to layers. The problem is that every layer you create requires GPU memory and management. On devices with limited memory the impact on performance can far outweigh the benefit of creating a layer. MDN discusses some of this in regard to will-change
property in a very cautionary manner.
Recently, the Web Animations API has recently become available in all evergreen browsers. MDN says that “it lets the browser make its own internal optimizations without hacks, coercion, or Window.requestAnimationFrame()
.”
I guess that means you’re off the hook for promoting elements to layers, but it does not state that fact explicitly. It will probably take more time before the API is used more widely before the implications are known properly.
Changing any property apart from transform
and opacity
always triggers painting. Paint is often the most expensive step of rendering, so it is a ripe area for optimization.
There are few things you can do:
box-shadow
is more expensive to paint than a property such a background-color
. Ask yourself if it’s possible to use a cheaper set of styles or alternative means to get to your end resultJavaScript runs on the browser’s main thread. If your JavaScript runs for a long time, it will block other rendering tasks, potentially causing frames to be dropped. You should be tactical about when JavaScript runs, and for how long it runs.
requestAnimationFrame
or the Web Animations API : You want to do the visual changes at the right time for the browser. The right time is at the start of the frame. The only way to guarantee that your JavaScript will run at the start of a frame is to use requestAnimationFrame
or the Web Animations API. Avoid using setTimeout or setInterval for animations. They are unreliable because they inherently run at some point in the frame and may introduce jank. JavaScript libraries use requestAnimationFrame under the hoodrequestAnimationFrame
handlers across each frame. You can also consider using web workers, which we discuss nextWeb workers (A.K.A. Dedicated Workers) are JavaScript’s version of threads. They give us the opportunity to run tasks in the background.
The main thread can be thought as the UI thread as that should be its focus. For smooth animation, you can’t afford to do extra work when an animation is running. If you need some data processing for an animation, you should consider moving the task to a web worker.
Good use cases are tasks such as data sorting, searching, and model generation.
var dataSortWorker = new Worker("sort-worker.js"); dataSortWorker.postMesssage(dataToSort); // The main thread is now free to continue working on other things... dataSortWorker.addEventListener('message', function(evt) { var sortedData = evt.data; // Update data on screen... });
Consider an animation that sorts a bar chart with a large dataset. If we push the data sorting to a web worker, you can ensure the bar chart is still responsive.
You can then choose to animate the changes incrementally. This will give the user appropriate feedback to ensure they know the progress of the task.
Samsur explored using web workers in a WebXR app in a recent article where he puts the physics computations into a web worker.
There are limitations with what you can do with a web worker. They don’t have access to some Web APIs such as the DOM. So, they are only suitable for certain types of tasks.
You need be careful when using input handlers with animations. If your animation is triggered when there is scrolling or responds to mouse or touch events, you need to consider a couple of things.
Some input events such as scrolling can trigger a hundred events per second. Is your scroll handler prepared for this rate of execution?
We don’t want to update an animation that quickly, that’s for sure! We can prevent this by debouncing or throttling.
Debouncing groups a sudden burst of events into one, so we don’t respond to events so frequently.
Throttling is a similar technique, but it looks to guarantee a constant frequency of executions every X milliseconds.
Sometimes, you can drop frames by triggering reflows. We want to avoid actions that result in a reflow-paint loop. Paul Lewis gives a good explanation of how to prevent this when he speaks about debouncing scroll events in animations.
The FLIP technique pre-optimizes an animation before execution. The idea is to invert the state of animations. Normally, we animate “straight ahead,” doing some expensive calculations on every single frame. FLIP precalculates the changes based on their final states. The first frame is an offset of the final state. This way the animation plays out in a much cheaper way.
This technique is particularly useful when an animation is triggered by user input. You can afford to do relatively expensive precalculation because you have a 100ms window where you’re able to do work without a user noticing. If you’re inside that window, users will feel like the response is instant.
It’s a different mental model for animation and takes some effort to get to grips with. FLIP reminds me of the movie Tenet, inverting the order of events!
You need to see an example to understand it fully. David Khourshid covers this topic in detail in his article Animating Layouts with the FLIP technique.
No! You shouldn’t feel constrained!
You will be surprised by how much you can achieve with the properties transform
and opacity
. Like a lot of creative endeavors, imposing some constraints can actually simplify your process and push you to be more creative. This codepen demonstrates some of what you can with just these 2 properties.
See the Pen
Performant Animations Cheatsheet by Rob (@robjoeol)
on CodePen.
Remember, feel free to use other properties too, the core message is: don’t reach for them first, and use them wisely!
I will briefly touch on the most useful contexts to use in Chrome DevTools.
To audit performance, you can find everything you need on the Performance tab.
Keep in mind that when looking at the frame rate, it’s only when things are moving that you need to maintain 60fps. So, there are natural troughs when nothing or very little is happening!
The radial pie graph in the Summary tab groups together the rendering activity. The groups correspond to the steps in the pixel pipeline, e.g., Rendering covers Layout and Style. The colors match my diagram. No coincidence! 😉
The Animations tab allows you to inspect animations and control their playback. You need to go to Settings to open this tab.
The Rendering tab is important for troubleshooting rendering. You need to go to Settings to open this tab. This can help you diagnose problems with paints, layers, and scrolling.
You can show an inset display of the frame rate stats by enabling Frame Rendering Stats. It’s helpful to have open while you are working on an animation.
You can read the docs for Chrome DevTools for more info. Firefox’s Devtools are excellent also.
Even if an animation is beautiful, the existence of jank can leave a bad taste with the user. Creating performant animations should not feel like a big and scary endeavor. It just takes being intentional about what you’re asking the browser to do every 16.7ms. If we do this, we give the browser the best shot to be successful at making smooth animations. Have fun animating!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCreate a multi-lingual web application using Nuxt 3 and the Nuxt i18n and Nuxt i18n Micro modules.
Use CSS to style and manage disclosure widgets, which are the HTML `details` and `summary` elements.
React Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.