Rob O'Leary Rob is a solution architect, fullstack developer, technical writer, and educator. He is an active participant in non-profit organizations supporting the underprivileged and promoting equality. He is travel-obsessed (one bug he cannot fix). You can find him at roboleary.net.

How to design highly performant animations and micro-interactions

10 min read 2893

Cellphone on Desk With Animations Playing

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!

Pull to Refresh Animation Example
Pull to refresh by Eugene Avdalyan.

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.

Why website speed matters

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.

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!

Experiences Graphs

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.

How website accessibility impacts design

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.

We made a custom demo for .
No really. Click here to check it out.

It’s important we don’t overlook or forget these facts along the way.

Using RAIL to target key metrics

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:

  • Response: Process events in under 50ms
  • Animation: Produce a frame in 10ms and aim for visual smoothness because users notice when frame rates vary
  • Idle: Maximize idle time to increase the odds that the page responds to user input within 50ms
  • Load: Deliver content and become interactive in under 5 seconds

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.

Trigger Animation

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.

Algorithms for improved rendering

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.

Rendering Waterfall

Each of the steps in the algorithm triggers those that come after:

  1. JavaScript: JavaScript triggers a visual change when there is a change made to the DOM or CSSOM
  2. Calculate Style: If the browser thinks that the computed styles for page elements have changed, it must then recalculate them
  3. Layout: Layout determines the dimensions and positions for each element based on the computed styles. It is also called “reflow” by Mozilla
  4. Paint: Painting fills in the pixels for each element onto layers. It doesn’t yet draw these layers onto the screen
  5. Composition: Draws the painted layers to screen in the correct order

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.

Designing highly performant animations and micro-interactions

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.

1. Try to stick to animating the composition properties 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.

Star Wars Animation

For example, this Star Wars animation effectively has 3 layers.

Star Wars Animation Layers

How do you promote an element to a layer?

  • In CSS: You can set the property will-change. For older browsers that don’t support will-change, you can add transform: translateZ(0); to an element as a hack
  • In JavaScript: Setting a transform with a 3D characteristic such as translate3d() and matrix3d() will create a layer. You may notice that some JavaScript libraries will do this under the hood

You 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.

2. Optimize paints

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:

  1. Promote elements that move or are regularly painted to their own layer: This follows on from our first heuristic. If an element is in its own layer, it doesn’t trigger painting of adjacent elements. But be selective in doing this and audit its effect on performance
  2. Reduce paint areas: You can reduce paint areas by ensuring that elements do not overlap as much. You can look for ways to avoid animating certain parts of the page. It is a challenge sometimes because browsers may union together two areas that need painting, and the entire screen may be repainted
  3. Simplify paint complexity: Painting some properties is more expensive. For example, anything that involves a blur such as 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 result

3. Optimize JavaScript execution

JavaScript 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.

  1. Use 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 hood
  2. Reduce complexity: Consider a batching approach where you divide a larger task into micro-tasks. Have each task take no longer than a few milliseconds, and run it inside of requestAnimationFrame handlers across each frame. You can also consider using web workers, which we discuss next

4. Use web workers for non-UI tasks

Web 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.

5. Debounce or throttle your input handlers

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.

Debouncing
Debouncing Leading codepen by Corbacho.

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.

6. Use the FLIP Technique

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.

Flip Technique

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.

Won’t these heuristics cramp my style?

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!

How to measure in DevTools

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.

Devtools 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.

Devtools Animations 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.

Devtools Rendering Tab

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.

Devtools Frame Stats

You can read the docs for Chrome DevTools for more info. Firefox’s Devtools are excellent also.

Conclusion

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!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Rob O'Leary Rob is a solution architect, fullstack developer, technical writer, and educator. He is an active participant in non-profit organizations supporting the underprivileged and promoting equality. He is travel-obsessed (one bug he cannot fix). You can find him at roboleary.net.

Leave a Reply