Instantly share code, notes, and snippets.

Embed
What would you like to do?

Explainer: Layout Stability Metric

Overview

Many websites suffer from "layout jank" - DOM elements shifting around due to content loading asynchronously.

We propose a way for the user agent to measure layout jank during a browsing session to compute a cumulative "jank score", which would be exposed by a new interface in the Performance API.

Jank Score

Each animation frame (a.k.a. "browsing context event loop") computes a "jank fraction" approximating the fraction of the viewport affected by layout jank during that frame. An animation frame with no layout jank has a jank fraction of 0.

The jank score is defined as the sum of the jank fractions of each animation frame that has occurred during the browsing session. The jank score is 0 when the page begins loading, and grows whenever jank occurs.

Janking Elements

A "janking element" is one whose visual representation starts in a significantly different location than it did in the previous animation frame. "Starts" refers here to the element's flow-relative offset in the document.

The visual representation of a block-level element is its border box. The visual representation of an inline element is the geometric union of its box fragments, the first of which determines its starting location.

Note that:

  • An element that changes in size (for example, by having children appended), but starts at the same offset, is not a janking element.

  • An element whose start location changes two or more times during the same animation frame (for example, from forced synchronous layouts), but is ultimately painted at the same location as the previous frame, is not a janking element.

  • An element whose start location changes by less than 3 CSS pixels is not a janking element. This allows elements to be animated, as long as the animation is smooth.

Jank Fraction

In general, the "jank fraction" of an animation frame is the fraction of the viewport that is occupied by the geometric union of the previous-frame and current-frame visual representations of all janking elements in that frame.

Illustration of a janking element on a device, with the jank region highlighted

Example: An element which occupies half the viewport moves by a distance equal to half its height. The jank fraction for this animation frame is 0.75.

However, if the user has generated certain UI events within the past 500 ms, the jank fraction is 0. This allows the page to modify its layout in response to the event. The event types that trigger this exception include taps, key presses, and mouse clicks, but not mousemove or events that cause scrolling.

The user agent may trade off precision for efficiency in the computation of jank fractions. It is intended that the jank fraction have a correspondence to the perceptual severity of the jank experience, but not that all user agents produce exactly the same jank scores for a given page.

Iframe and Aggregate Jank Score

The aggregate jank score of a top-level browsing context can be computed by adding its own jank score to the weighted jank scores of its descendant browsing contexts, such as those created by <iframe> elements.

In performing this aggregation, the jank score of an <iframe> should be weighted by the fraction of the top-level viewport it occupies.

Performance API

An animation frame with a non-zero jank fraction adds a LayoutJank entry to the performance timeline.

interface LayoutJank : PerformanceEntry {
    readonly attribute double fraction;
};

The entry's fraction attribute is the jank fraction. Its entryType attribute is "layoutjank".

The developer can compute the jank score at any time by summing the jank fractions:

var jankScore = performance.getEntriesByType("layoutjank").reduce(
    (score, fraction) => { return score + fraction; }, 0);

Note that this computes the jank score of the current browsing context, but not the aggregate jank score incorporating subframes. (TODO: do we need something better?)

In addition, the developer can create a PerformanceObserver to be notified when new layout jank entries are added to the timeline.

new PerformanceObserver((list) => {
    list.getEntries().forEach((entry) => { /* ... */ });
}).observe({entryTypes: ["layoutjank"]});

Links

@jesup

This comment has been minimized.

Copy link

jesup commented Nov 28, 2018

I'm concerned that (similar to timing issues related to iframes) that including the layout-jank scores from inside iframes exposes cross-domain information about the contents of the iframe. This will need a privacy/security pass. If iframes have to be excluded, I suspect that doesn't obviate the usefulness of this proposal, though it may limit knowledge of layout jank for iframe-dependent pages.

@gregwhitworth

This comment has been minimized.

Copy link

gregwhitworth commented Dec 21, 2018

Thanks for taking the time to write this up - Todd Reifsteck reached out to me to take a glance at this, sorry for the delay. I don't want to spend too much time on bikeshedding, but I got kind of excited when I read the name layoutJank only to find out that it is a very scoped scenario, one in which there are a few different solutions (CSSWG is looking at aspect-ratio for example) that may help provide web developers with ways to avoid this specific source of jank. So I think, at the very least - renaming it to scrollLayoutJank or something similar would aid in its intuitiveness.

With regards to this statement:

An element that changes in size (for example, by having children appended), but starts at the same offset, is not a janking element.

I'm curious as to why this assumption is made. In many of the perf scenarios I've helped webdevs with, jank is rarely a constant occurrence, which the current verbiage seems to be written with the expectation that it is (based on the defined example and the one linked in the bug, I can understand that this API is indeed defined around one or two very narrow use cases). It is often the case that upon a creation of a very dense DOM does the jank reveal itself due to increased workload of the rest of the pipeline as a result (of which, it may be the cascade at fault and not layout [another reason I'd avoid the term Layout in the name]).

However, if the user has generated certain UI events within the past 500 ms

This seems like a reasonable default, but something that I'd recommend having an option to remove. Many end user interactions that web developers will want to test for jank is going to be following end user interaction outside of scrolling. For example, let's assume twitter wants to add a fancier animation when new items are loaded into the feed? On their desktop site, this is done following the click of "Load new tweets." This is a valid scenario to watch for a jank-free end user experience but currently would not be watched by this observer.

An element whose start location changes by less than 3 CSS pixels is not a janking element. This allows elements to be animated, as long as the animation is smooth.

I assume this is due to wanting some threshold, but it kind of feels like a stab in the dark so I think if this is something that you do want to include in this then you should allow it to be changed by the author.

Personally, I think there is a solid desire from web developers to be able to observe end user jank and being able to understand the role that each aspect of the pipeline played in that jank. However, I think this approach is starting from the wrong end here, which is trying to define jank - and in a very narrow usecase at that. Starting with jank isn't the best approach as it's a subjective metric as one persons jank is another person's smooth. I think what would be much more valuable is a bag of measures that mirrors what web developers are already used to seeing in the DEV tools.

  • SCRIPT
  • DOM
  • STYLE
  • LAYOUT
  • PAINT

The web developer can then utilize this object with the other Performance.* methods to define and react to their own definition of Jank in a given scenario. Sorry for writing a tome :)

@skobes

This comment has been minimized.

Copy link
Owner

skobes commented Jan 10, 2019

Thanks for the comments!

The term "jank" may be misleading here, since it's also used to talk about slowness. The goal of this API isn't to detect slowness of rendering stages like script / dom / style / layout / paint. It's only aimed at detecting layout shifts. Perhaps the entryType should be "layoutshift" or "layoutchange".

APIs like aspect-ratio complement this feature by giving developers tools to avoid layout shifts as well as measure them.

I like the idea of configurability for parameters like the input timeout and distance threshold.

The reason we care about offset changes but not size changes is that we don't want to penalize normal incremental loading. For example, consider an entirely static page of text loading over a slow network. The initial layouts will be done on an incomplete page and the <body> element will grow as more content is delivered. But we wouldn't want the expansion of the body element to be treated as layout instability for the purpose of this metric.

@tdresser

This comment has been minimized.

Copy link

tdresser commented Jan 10, 2019

I agree that "Jank" isn't the best term here.
Something like "Layout Stability" is, I think, a better term.

"So I think, at the very least - renaming it to scrollLayoutJank or something similar would aid in its intuitiveness."
This isn't scrolling related - it's about content moving around within the viewport.

"Starting with jank isn't the best approach as it's a subjective metric as one persons jank is another person's smooth."
For layout stability, I don't think this this is the case.
This metric is defined so that for most pages, there should be 0 layout instability.
There are some tricky animated cases that are a bit fuzzy, but the general case is very clear.

@npm1

This comment has been minimized.

Copy link

npm1 commented Jan 14, 2019

From discussion at the call, it seems that the naming was the only objection here. Here are some options for the name:
Prefix: Content, Dom, Layout
Suffix: Instability, Movement, Shift

The score could also be inverted and we could invert the naming correspondingly (for example, LayoutStability). Any thoughts on which of these would be a good name, if any? Other proposals welcome.

@bgirard

This comment has been minimized.

Copy link

bgirard commented Jan 14, 2019

I voiced some concerns in Lyon on the term 'Jank'. Personally I think Layout Instability works best. DOM Instability to me could just be changes to the DOM tree structure of the page that may or not have visible layout implications so I think the Layout prefix is more descriptive.

@paulirish

This comment has been minimized.

Copy link

paulirish commented Jan 14, 2019

I personally prefer "Layout Stability", followed by "Layout instability".

I can't think of a prefix that's reasonable except for "Layout", for reasons like what @bgirard shared.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment