Towards an animation smoothness metric  |  Articles  |  web.dev (2024)

Learn about measuring animations, how to think about animation frames, and overall page smoothness.

Towards an animation smoothness metric | Articles | web.dev (1)

Behdad Bakhshinategh

Towards an animation smoothness metric | Articles | web.dev (2)

Jonathan Ross

Towards an animation smoothness metric | Articles | web.dev (3)

Michal Mocny

You've probably experienced pages that "stutter" or "freeze" during scrolling oranimations. We like to say that these experiences are not smooth. To addressthese types of issues, the Chrome team has been working on adding more supportto our lab tooling for animation detection, as well as making steady improvementsto the rendering pipeline diagnostics within Chromium.

We'd like to share some recent progress, offer concrete tooling guidance, anddiscuss ideas for future animation smoothness metrics. As always, we would loveto hear your feedback.

This post will cover three main topics:

  • A quick look at animations and animation frames.
  • Our current thoughts on measuring overall animation smoothness.
  • A few practical suggestions for you to leverage in lab tooling today.

What are animations?

Animations bring content to life! By making content move, especially in responseto user interactions, animations can make an experience feel more natural,understandable, and fun.

But poorly implemented animations, or just adding too many animations, candegrade the experience and make it decidedly not fun at all. We've probably allinteracted with an interface which just added too many "helpful" transitioneffects, which actually become hostile to experience when they perform poorly.Some users therefore actually mightprefer reduced motion, a user preferencethat you should honor.

How do animations work?

As a quick recap, the rendering pipelineconsists of a few, sequential stages:

  1. Style: Calculate thestyles that apply to the elements.
  2. Layout: Generate thegeometry and position for each element.
  3. Paint: Fill out thepixels for each element into layers.
  4. Composite: Draw thelayers to the screen.

While there are many ways to define animations, they all fundamentally work viaone of the following:

  • Adjusting layoutproperties.
  • Adjusting paintproperties.
  • Adjusting compositeproperties.

Because these stages are sequential, it is important to define animations interms of properties that are further down the pipeline. The earlier the updatehappens in the process, the greater are the costs and it's less likely to besmooth. (See Renderingperformancefor more details.)

While it can be convenient to animate layout properties, there are costs todoing so, even if those costs aren't immediately apparent. Animations should bedefined in terms of composite property changes wherever possible.

Defining declarative CSS animations or using WebAnimations,and ensuring you animate compositeproperties,is a great first step to ensuring smooth and efficient animations. But still,this alone does not guarantee smoothness because even efficient web animationshave performance limits. That's why it is always important to measure!

What are animation frames?

Updates to the visual representation of a page take time to appear. A visualchange will lead to a new animation frame, which is eventually rendered on theuser's display.

Displays update on some interval, so visual updates are batched. Many displaysupdate on a fixed interval of time, such as 60 times a second (that is60Hz). Some more modern displays can offer higher refresh rates(90–120Hz are becoming common). Often these displays can actively adaptbetween refresh rates as needed, or even offer fully variable frame rates.

The goal for any application, like a game or a browser, is to process all thesebatched visual updates and produce a visually complete animation frame withinthe deadline, every time. Note that this goal is entirely distinct from otherimportant browser tasks such as loading content from the network quickly orexecuting JavaScript tasks efficiently.

At some point, it can become too difficult to complete all visual updates withinthe allotted deadline assigned by the display. When this happens, the browserdrops a frame. Your screen doesn't go black, it just repeats itself. You seethe same visual update for a bit longer—the same animation frame that waspresented at the previous frame opportunity.

This actually happens often! It is not necessarily even perceptible, especiallyfor static or document-like content, which is common on the web platform inparticular. Dropped frames only become apparent when there are important visualupdates, such as animations, for which we need a steady stream of animationupdates to show smooth motion.

What impacts animation frames?

Web developers can greatly impact the ability of a browser to quickly andefficiently render and present visual updates!

Some examples:

  • Using content that is too large or resource-intensive to decode quickly on thetarget device.
  • Using too manylayersrequiring too much GPU memory.
  • Defining overly complex CSS styles or web animations.
  • Using design anti-patterns that disable fast rendering optimizations.
  • Too much JS work on the main thread, leading to long tasks that block visualupdates.

But how can you know when an animation frame has missed its deadline and causeda dropped frame?

One possible method is usingrequestAnimationFrame()polling, however it has several downsides. requestAnimationFrame(), or "rAF",tells the browser that you wish to perform an animation and asks for anopportunity to do so before the next paint stage of the rendering pipeline. Ifyour callback function isn't called at the time you expect it, that means apaint wasn't executed, and one or more frames were skipped. By polling andcounting how often rAF is called, you can compute a sort of "frames per second"(FPS) metric.

let frameTimes = [];function pollFramesPerSecond(now) { frameTimes = [...frameTimes.filter(t => t > now - 1000), now]; requestAnimationFrame(pollFramesPerSecond); console.log('Frames per second:', frameTimes.length);}requestAnimationFrame(pollFramesPerSecond);

Using requestAnimationFrame() polling is not a good idea for several reasons:

  • Every script has to set up its own polling loop.
  • It can block the critical path.
  • Even if the rAF polling is fast, it can preventrequestIdleCallback()from being able to schedule long idle blocks when used continuously (blocks thatexceed a single frame).
  • Similarly, lack of long idle blocks prevents the browser from scheduling otherlong-running tasks (such as longer garbage collection and other background orspeculative work).
  • If polling is toggled on and off, then you'll miss cases where frame budgethas been exceeded.
  • Polling will report false-positives in cases where the browser is usingvariable update frequency (for example, due to power or visibility status).
  • And most importantly, it doesn't actually capture all types of animationupdates!

Too much work on the main thread can impact the ability to see animation frames.Check out the JankSample to see how arAF-driven animation, once there is too much work on the main thread (such aslayout), will lead to dropped frames and fewer rAF callbacks, and lower FPS.

When the main thread becomes bogged down, visual updates begin to stutter.That's jank!

Many measurement tools have focused extensively on the ability for the mainthread to yield in a timely manner, and for animation frames to run smoothly.But this is not the whole story! Consider the following example:

The video above shows a page that periodically injects long tasks onto the mainthread. These long tasks completely ruin the ability of the page to providecertain types of visual updates, and you can see in the top-left corner acorresponding drop of requestAnimationFrame() reported FPS to 0.

And yet, despite these long tasks, the page continues to scroll smoothly. Thisis because on modern browsers, scrolling is oftenthreaded,driven entirely by the compositor.

This is an example that simultaneously contains many dropped frames on the mainthread, yet still has many successfully-delivered frames of scrolling on thecompositor thread. Once the long task is complete, the main thread paint updatehas no visual change to offer anyway. rAF polling suggested a frame drop to 0,but visually, a user wouldn't be able to notice a difference!

For animation frames, the story is not that simple.

Animation frames: Updates that matter

The above example showcases that there is more to the story than justrequestAnimationFrame().

So when do animation updates and animation frames matter? Here aresome criteria we're thinking about and would love to get feedback on:

  • Main and compositor thread updates
  • Missing paint updates
  • Detecting animations
  • Quality versus quantity

Main and compositor thread updates

Animation frame updates are not boolean. It is not the case that frames may onlybe fully dropped or fully presented. There are many reasons why an animationframe may be partially presented. In other words, it can simultaneously havesome stale content while also having some new visual updates which arepresented.

The most common example of this is when the browser is unable to produce a newmain thread update within frame deadline but does have a new compositor threadupdate (such as the threaded scrolling example from earlier).

One important reason why using declarative animations to animate compositeproperties is recommended is that doing so enables an animation to be drivenentirely by the compositor thread even when the main thread is busy. These typesof animations can continue to produce visual updates efficiently and inparallel.

On the other hand, there may be cases where a main thread update finally becomesavailable for presentation, but only after missing several frame deadlines. Herethe browser will have some new update, but it may not be the very latest.

Broadly speaking, we consider frames that contain some new visual updates,without all new visual updates, as a partial frame. Partial frames are fairlycommon. Ideally, partial updates would at least include the most importantvisual updates, like animations, but that can only happen if animations aredriven by the compositor thread.

Missing paint updates

Another type of partial update is when media like images have not finisheddecoding and rasterizing in time for frame presentation.

Or, even if a page is perfectly static, browsers may still fall behind renderingvisual updates during rapid scrolling. That is because the pixel renditions ofcontent beyond the visible viewport may be discarded to save GPU memory. Ittakes time to render pixels, and it may take longer than a single frame torender everything after a large scroll, like a finger fling. This is commonlyknown as checkerboarding.

With each frame rendering opportunity, it's possible to track how much of thelatest visual updates actually got to the screen. Measuring the ability to do soover many frames (or time) is broadly known as frame throughput.

If the GPU is really bogged down, the browser (or platform) may even begin tothrottle the rate at which it attempts visual updates and thus decreaseseffective frame rates. While technically that can reduce the number of droppedframe updates, visually it will still appear as a lower frame throughput.

Yet, not all types of low frame throughput are bad. If the page is mostly idleand there are no active animations, a low frame rate is just as visuallyappealing as a high frame rate (and, it can save battery!).

So when does frame throughput matter?

Detecting animations

High frame throughput matters especially during periods with importantanimations. Different animation types will depend on visual updates from aspecific thread (main, compositor, or a worker), so its visual update isdependent on that thread providing its update within the deadline. We say that agiven thread affects smoothness whenever there is an active animation thatdepends on that thread update.

Some types of animations are easier to define and detect than others.Declarative animations, or user-input-driven animations, are clearer to definethan JavaScript-driven animations implemented as periodic updates to animatablestyle properties.

Even with requestAnimationFrame() youcannot always assume that every rAF call is necessarily producing a visualupdate or animation. For example, using rAF polling just to track frame rate(as shown above) should not itself affect smoothness measurements since there isno visual update.

Quality versus quantity

Finally, detecting animations and animation frame updates is still only part ofthe story because it only captures the quantity of animation updates, not thequality.

For example, you may see a steady framerate of of 60fps while watching avideo. Technically, this is perfectly smooth, but the video itself may have alow bit rate, or issues with network buffering. This isn't captured byanimation smoothness metrics directly, yet may still be jarring to theuser.

Or, a game which leverages <canvas> (perhaps even using techniques likeoffscreencanvas toensure a steady frame rate) may technically be perfectly smooth in terms ofanimation frames, all while failing to load high quality game assets into thescene or exhibiting rendering artifacts.

And of course, a site can just have some really bad animations 🙂

Towards an animation smoothness metric | Articles | web.dev (4)

I mean, I guess they were pretty cool for their time!

States of a single animation frame

Because frames may be partially presented, or dropped frames may happen in waysthat do not affect smoothness, we have begun to think of each frame as having acompleteness or smoothness score.

Here is the spectrum of ways in which we interpret the state of a singleanimation frame, ordered from best to worst case:

No Update Desired Idle time, repeat of the previous frame.
Fully presented The main thread update was either committed within deadline, or no main thread update was desired.
Partially presented Compositor only; the delayed main thread update had no visual change.
Partially presented Compositor only; the main thread had a visual update, but that update did not include an animation that affects smoothness.
Partially presented Compositor only; the main thread had a visual update that affects smoothness, but a previously stale frame arrived and was used instead.
Partially presented Compositor only; without the desired main update, and the compositor update has an animation that affects smoothness.
Partially presented Compositor only but the compositor update does not have an animation that affects smoothness.
Dropped frame No update. There was no compositor update desired, and main was delayed.
Dropped frame A compositor update was desired, but it was delayed.
Stale frame An update was desired, it was produced by the renderer, but the GPU still did not present it before the vsync deadline.

It's possible to turn these states into somewhat of a score. And perhaps one wayto interpret this score is to consider it a probability of being observable bythe user. A single dropped frame may not be very observable, but a sequence ofmany dropped frames affecting smoothness in a row sure is!

Putting it all together: A Percent Dropped Frames metric

While it can sometimes be necessary to dive deep into the state of eachanimation frame, it's also useful to just assign a quick "at a glance" score foran experience.

Because frames may be partially presented, and because even fully skippedframe updates may not actually affect smoothness, we want to focus less onjust counting frames, and more on the extent to which the browser is unable toprovide visually complete updates when it mattered.

The mental model should move from:

  1. Frames Per Second, to
  2. Detecting missing and important animation updates, to
  3. Percentage Dropped over a given time period.

What matters is: the proportion of time waiting for importantupdates. We think this matches the natural way users experience the smoothnessof web content in practice. So far, we have been using the following as aninitial set of metrics:

  • Average Percent Dropped: for all non-idle animation frames throughout thewhole timeline
  • Worst Case of Percent Dropped Frames: as measured over 1 second slidingwindows of time.
  • 95th percentile of Percent Dropped Frames: as measured over 1 secondsliding windows of time.

You can find these scores in some Chrome developer tools today. While thesemetrics focus only on overall frame throughput, we are also evaluating otherfactors, such as frame latency.

Performance HUD

Chromium has a neat Performance HUD hidden behind a flag(chrome://flags/#show-performance-metrics-hud). In it, you can find livescores for things like Core Web Vitals and also a few experimental definitionsfor animation smoothness based on Percent Dropped Frames over time.

Towards an animation smoothness metric | Articles | web.dev (5)

Frame Rendering Stats

Enable "Frame RenderingStats"in DevTools via Rendering settings to see a live view of new animation frames,which are color-coded to differentiate partial updates from fully-dropped frameupdates. The reported fps is for fully-presented frames only.

Towards an animation smoothness metric | Articles | web.dev (6)

Frame Viewer in DevTools performance profile recordings

The DevTools Performancepanel haslong had a Framesviewer.However, it had grown a bit out of sync with how the modern rendering pipelineactually works. There has been plenty of recent improvement, even in the latestChrome Canary, which we think will greatly ease debugging animation issues.

Today you will find that frames in the frames viewer are better aligned withvsync boundaries, and color-coded based on status. There is still not fullvisualization for all the nuances outlined above, but we're planning to add morein the near future.

Towards an animation smoothness metric | Articles | web.dev (7)

Chrome tracing

Finally, with Chrome Tracing, the tool of choice for diving deep into details,you can record a "Web content rendering" trace via the new PerfettoUI (or about:tracing), and dig deep into Chrome'sgraphicspipeline. It can be a daunting task, but there are a few thingsrecently added to Chromium to make it easier. You can get an overview of what isavailable in the Life of aFramedocument.

Through trace events you can definitively determine:

  • Which animations are running (using events named TrackerValidation).
  • Getting the exact timeline of animation frames (using events namedPipelineReporter).
  • For janky animation updates, figure out exactly what is blocking youranimation from running faster (using the event breakdowns withinPipelineReporter events).
  • For input-driven animations, see how long it takes to get a visual update(using events named EventLatency).

Towards an animation smoothness metric | Articles | web.dev (8)

What's next

The Web Vitals initiative aims to provide metric and guidance forbuilding great user experiences on the web.Lab-based metrics like TotalBlocking Time (TBT) are vital forcatching and diagnosing potential interactivity issues. We are planning todesign a similar lab-based metric for animation smoothness.

We'll keep you posted as we continue to work through ideas for designing acomprehensive metric based on individual animation frame data.

In the future, we'd also like to design APIs that make it possible to measureanimation smoothness performantly for real users in thefield as well as in the lab.Stay tuned for updates there as well!

Feedback

We're excited about all the recent improvements and developer tools that haveshipped in Chrome to measure animation smoothness. Please try these tools out,benchmark your animations, and let us know where it leads!

You can send your comments to theweb-vitals-feedback Googlegroup with "[Smoothness Metrics]" in the subject line. We're really lookingforward to hearing what you think!

Towards an animation smoothness metric  |  Articles  |  web.dev (2024)
Top Articles
Latest Posts
Article information

Author: Greg O'Connell

Last Updated:

Views: 6384

Rating: 4.1 / 5 (42 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Greg O'Connell

Birthday: 1992-01-10

Address: Suite 517 2436 Jefferey Pass, Shanitaside, UT 27519

Phone: +2614651609714

Job: Education Developer

Hobby: Cooking, Gambling, Pottery, Shooting, Baseball, Singing, Snowboarding

Introduction: My name is Greg O'Connell, I am a delightful, colorful, talented, kind, lively, modern, tender person who loves writing and wants to share my knowledge and understanding with you.