Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document Layout Instability API #23007

Merged
merged 19 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions files/en-us/_redirects.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8748,6 +8748,7 @@
/en-US/docs/Web/API/KeyframeEffectReadOnly/iterationComposite /en-US/docs/Web/API/KeyframeEffect/iterationComposite
/en-US/docs/Web/API/KeyframeEffectReadOnly/target /en-US/docs/Web/API/KeyframeEffect/target
/en-US/docs/Web/API/Largest_Contentful_Paint_API /en-US/docs/Web/API/LargestContentfulPaint
/en-US/docs/Web/API/Layout_Instability_API /en-US/docs/Web/API/LayoutShift
/en-US/docs/Web/API/LinearAccelerationSensor/x /en-US/docs/Web/API/Accelerometer/x
/en-US/docs/Web/API/LinearAccelerationSensor/y /en-US/docs/Web/API/Accelerometer/y
/en-US/docs/Web/API/LinearAccelerationSensor/z /en-US/docs/Web/API/Accelerometer/z
Expand Down
12 changes: 12 additions & 0 deletions files/en-us/glossary/cumulative_layout_shift/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Cumulative Layout Shift (CLS)
slug: Glossary/Cumulative_Layout_Shift
---

**Cumulative Layout Shift** (CLS) is a score that is the sum of every layout shift value is recorded. Layout shifts happen when any element that is visible in the viewport changes its position between two frames.

It is measured using the {{domxref("LayoutShift")}} API and a {{domxref("PerformanceObserver")}} by summing the values as they reported to the observer. The final score is taken at the time of the [`visibilitychange` event](/en-US/docs/Web/API/Document/visibilitychange_event).
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

## See also

- {{domxref("LayoutShift")}}
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
49 changes: 0 additions & 49 deletions files/en-us/web/api/layout_instability_api/index.md

This file was deleted.

54 changes: 54 additions & 0 deletions files/en-us/web/api/layoutshift/hadrecentinput/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: LayoutShift.hadRecentInput
slug: Web/API/LayoutShift/hadRecentInput
page-type: web-api-instance-property
browser-compat: api.LayoutShift.hadRecentInput
tags:
- experimental
---

{{SeeCompatTable}}{{APIRef("Performance API")}}

The **`hadRecentInput`** readonly property of the {{domxref("LayoutShift")}} interface returns `true` if {{domxref("LayoutShift.lastInputTime", "lastInputTime")}} is less than 500 milliseconds in the past.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

## Value

A boolean returning `true` if {{domxref("LayoutShift.lastInputTime", "lastInputTime")}} is less than 500 milliseconds in the past; `false` otherwise.

## Examples

### Ignoring recent user input for CLS score

The following example shows how the `hadRecentInput` property is used to only count layout shifts without recent user input.

```js
let cumulativeLayoutShiftScore = 0;

const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Count layout shifts without recent user input only
if (!entry.hadRecentInput) {
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
cumulativeLayoutShiftScore += entry.value;
console.log("Current CLS value:", cumulativeLayoutShiftScore, entry);
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
if (entry.sources) {
for (const { node, curRect, prevRect } of entry.sources)
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
console.log("Shift source:", node, { curRect, prevRect });
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
});

observer.observe({ type: "layout-shift", buffered: true });
```

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- {{domxref("LayoutShift.lastInputTime")}}
78 changes: 53 additions & 25 deletions files/en-us/web/api/layoutshift/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,41 @@ tags:
browser-compat: api.LayoutShift
---

{{APIRef("Layout Instability API")}}{{SeeCompatTable}}
{{APIRef("Performance API")}}{{SeeCompatTable}}

The `LayoutShift` interface of the [Layout Instability API](/en-US/docs/Web/API/Layout_Instability_API) provides insights into the stability of web pages based on movements of the elements on the page.
The `LayoutShift` interface of the provides insights into the layout stability of web pages based on movements of the elements on the page.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

## Description

A layout shift happens when any element that is visible in the viewport changes its position between two frames. These elements are described as being **unstable**, and contribute to a poor {{Glossary("Cumulative Layout Shift")}} (CLS) score, indicating a lack of visual stability.

The Layout Instability API provides a way to measure and report on these layout shifts. All tools for debugging layout shifts, including those in browser's developer tools, use this API. The API can also be used to observe and debug layout shifts by logging the information to the console, to send the data to a server endpoint, or to web page analytics.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

{{InheritanceDiagram}}

## Instance properties

This interface extends the following {{domxref("PerformanceEntry")}} properties for `layout-shift` performance entry types by qualifying them as follows:
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

- {{domxref("PerformanceEntry.duration")}} {{ReadOnlyInline}} {{Experimental_Inline}}
- : Always returns `0` (the concept of duration does not apply to layout shifts).
- {{domxref("PerformanceEntry.entryType")}} {{ReadOnlyInline}} {{Experimental_Inline}}
- : Always returns `"layout-shift"`
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
- {{domxref("PerformanceEntry.name")}} {{ReadOnlyInline}} {{Experimental_Inline}}
- : Always returns `"layout-shift"`
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
- {{domxref("PerformanceEntry.startTime")}} {{ReadOnlyInline}} {{Experimental_Inline}}
- : Returns a {{domxref("DOMHighResTimeStamp")}} representing the time when the layout shift started.

This interface also supports the following properties:

- {{domxref("LayoutShift.value")}} {{Experimental_Inline}}
- : Returns the `impact fraction` (fraction of the viewport that was shifted) times the `distance fraction` (distance moved as a fraction of viewport).
- : Returns the layout shift score calculated by the impact fraction (fraction of the viewport that was shifted) times the distance fraction (distance moved as a fraction of viewport).
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
- {{domxref("LayoutShift.hadRecentInput")}} {{Experimental_Inline}}
- : Returns `true` if there was a user input in the past 500 milliseconds.
- : Returns `true` if {{domxref("LayoutShift.lastInputTime", "lastInputTime")}} is less than 500 milliseconds in the past.
- {{domxref("LayoutShift.lastInputTime")}} {{Experimental_Inline}}
- : Returns the time of the most recent user input.
- : Returns the time of the most recent excluding input or `0` if no excluding input has occurred.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
- {{domxref("LayoutShift.sources")}} {{Experimental_Inline}}
- : Returns an array of {{domxref('LayoutShiftAttribution')}} objects with information on the elements that were shifted.
- : Returns an array of {{domxref("LayoutShiftAttribution")}} objects with information on the elements that were shifted.

## Instance methods

Expand All @@ -38,39 +57,43 @@ The `LayoutShift` interface of the [Layout Instability API](/en-US/docs/Web/API/

## Examples

The following example shows how to capture layout shifts and log them to the console.
### Logging the current CLS score

Note that in this example data is only sent to the server when the user leaves the tab.
The following example shows how to capture layout shifts and log them to the console.

```js
// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
let cumulativeLayoutShiftScore = 0;
let cumulativeLayoutShiftScore = 0;

const observer = new PerformanceObserver((list) => {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Only count layout shifts without recent user input.
// Count layout shifts without recent user input only
if (!entry.hadRecentInput) {
cumulativeLayoutShiftScore += entry.value;
console.log("Current CLS value:", cumulativeLayoutShiftScore, entry);
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
if (entry.sources) {
for (const { node, curRect, prevRect } of entry.sources)
console.log("Shift source:", node, { curRect, prevRect });
}
}
}
});
});

observer.observe({type: 'layout-shift', buffered: true});
observer.observe({ type: "layout-shift", buffered: true });
```

### Measuring the final CLS score

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// Force any pending records to be dispatched.
The final CLS score is taken at the time of the [`visibilitychange` event](/en-US/docs/Web/API/Document/visibilitychange_event) for which you can add an event handler.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

```js
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
// Force any pending records to be dispatched
observer.takeRecords();
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
observer.disconnect();

console.log('CLS:', cumulativeLayoutShiftScore);
console.log("CLS:", cumulativeLayoutShiftScore);
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
}
});
} catch (e) {
// Do nothing if the browser doesn't support this API.
}
});
```

## Specifications
Expand All @@ -80,3 +103,8 @@ try {
## Browser compatibility

{{Compat}}

## See also

- {{domxref("LayoutShiftAttribution")}}
- {{Glossary("Cumulative Layout Shift")}}
54 changes: 54 additions & 0 deletions files/en-us/web/api/layoutshift/lastinputtime/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: LayoutShift.lastInputTime
slug: Web/API/LayoutShift/lastInputTime
page-type: web-api-instance-property
browser-compat: api.LayoutShift.lastInputTime
tags:
- experimental
---

{{SeeCompatTable}}{{APIRef("Performance API")}}

The **`lastInputTime`** readonly property of the {{domxref("LayoutShift")}} interface returns the time of the most recent excluding input or `0` if no excluding input has occurred.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

Excluding inputs are:
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

- Any events which signals a user's active interactive with the document ([`mousedown`](/en-US/docs/Web/API/Element/mousedown_event), [`keydown`](/en-US/docs/Web/API/Element/keydown_event), [`pointerdown`](/en-US/docs/Web/API/Element/pointerdown_event))
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved
- Any events which directly changes the size of the viewport.
- [`change`](/en-US/docs/Web/API/HTMLElement/change_event) events.

The [`mousemove`](/en-US/docs/Web/API/Element/mousemove_event) and [`pointermove`](/en-US/docs/Web/API/Element/pointermove_event) events are **not** excluding inputs.

## Value

A {{domxref("DOMHighResTimeStamp")}} indicating the most recent excluding input time or `0` if no excluding input has occurred.

## Examples

### Logging last input times

Log excluding input times if excluding input has occurred.

```js
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.lastInputTime) {
console.log(entry.lastInputTime);
}
});
});

observer.observe({ type: "layout-shift", buffered: true });
```

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- {{domxref("LayoutShift.hadRecentInput")}}
44 changes: 44 additions & 0 deletions files/en-us/web/api/layoutshift/sources/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: LayoutShift.sources
slug: Web/API/LayoutShift/sources
page-type: web-api-instance-property
browser-compat: api.LayoutShift.sources
tags:
- experimental
---

{{SeeCompatTable}}{{APIRef("Performance API")}}

The **`sources`** readonly property of the {{domxref("LayoutShift")}} interface returns an array of {{domxref("LayoutShiftAttribution")}} objects that indicate the DOM elements that moved during the layout shift.
Elchi3 marked this conversation as resolved.
Show resolved Hide resolved

## Value

An {{jsxref("Array")}} of {{domxref("LayoutShiftAttribution")}} objects. This array will not contain more than five sources. If there are more than five elements impacted by the layout shift, the five most impactful elements are reported.

## Examples

### Logging layout shift sources

```js
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
entry.sources.forEach((source) => {
console.log(source);
});
});
});

observer.observe({ type: "layout-shift", buffered: true });
```

## Specifications

{{Specifications}}

## Browser compatibility

{{Compat}}

## See also

- {{domxref("LayoutShiftAttribution")}}
Loading