1. Introduction
This section is non-normative. The LargestContentfulPaint API enables developers to gain visibility into the loading and rendering process of the web pages, in order for them to be able to optimize it.
Developers need a reliable metric that correlates with their user’s visual rendering experience. Paint loading metrics such as First Paint and First Contentful Paint focus on initial rendering, but don’t take into account the importance of the painted content, and therefore may indicate times in which the user still does not consider the page useful.
Largest Contentful Paint (LCP) aims to be a page-load metric that:
-
better correlates with user experience than First Paint and First Contentful Paint
-
is easy to understand and reason about
-
reduces the chance of gaming
The largest paint during the loading process of the page is likely to signify a meaningful event from the user’s perspective, and is therefore something we want to expose by default to developers, enabling performance teams, analytics providers and lab-based measurement tools to collect those metrics without requiring extra annotation work by the folks creating the content itself.
The API relies heavily on concepts defined in [PAINT-TIMING], which can be thought of as the low-level primitive that this high-level feature is built on top of. For cases where the content creators are willing to annotate their content and indicate the important points in the page’s loading cycle, [ELEMENT-TIMING] is the API that will provide them more control over the elements that get reported.
NOTE: The Largest Contentful Paint API will only expose elements that are timing-eligible. Note that unlike Element Timing, there is no need to annotate elements in order to have them be eligible for Largest Contentful Paint.
1.1. Largest content
The algorithm used for this API keeps track of the content seen so far. Whenever a new largest content is found, a new entry is created for it. Content that is removed is still considered by the algorithm. In particular, if the content removed was the largest, then a new entry is created only if larger content is ever added. The algorithm terminates whenever scroll or input events occur, since those are likely to introduce new content into the website.
1.2. Usage example
The following example shows an image and a large body of text. The developer then registers an observer that gets candidate entries for the largest paint while the page is loading.
< img src = "large_image.jpg" > < p id = 'large-paragraph' > This is large body of text.</ p > ...< script > const observer= new PerformanceObserver(( list) => { let perfEntries= list. getEntries(); let lastEntry= perfEntries[ perfEntries. length- 1 ]; // Process the latest candidate for largest contentful paint }); observer. observe({ entryTypes: [ 'largest-contentful-paint' ]}); </ script >
1.3. Limitations
The LargestContentfulPaint API is based on heuristics. As such, it is error prone. It has the following problems:
-
The algorithm halts when it detects certain types of user inputs. However, this means that the algorithm will not capture the main content if the user input occurs before the main content is displayed. In fact, the algorithm may produce meaningless results or no results at all if user input occurs very early.
-
To account for image carousels, content is still considered as the largest even if it’s removed. This presents problems for websites with splash screens that use large content as placeholders.
2. Terminology
A largest contentful paint candidate is a struct containing the following members:
An largest contentful paint candidate candidate is eligible to be largest contentful paint if it meets the following criteria:
-
candidate’s element's opacity is > 0
-
candidate’s element is a text node, or candidate’s request's response's content length in bytes is >= candidate’s element's effective visual size * 0.004
Note: This heuristic tests whether the image resource contains sufficient data to be seen as contentful to the user. It compares the transferred file size with the number of pixels which are actually produced, after decoding and any image scaling is applied. Images which encode a very large number of pixels with in a very small number of bytes are typically low-content backgrounds, gradients, and the like, and are not considered as largest contentful paint candidates.
3. Largest Contentful Paint
Largest Contentful Paint involves the following new interface:
3.1. LargestContentfulPaint
interface
[Exposed =Window ]interface :
LargestContentfulPaint PerformanceEntry {readonly attribute DOMHighResTimeStamp ;
renderTime readonly attribute DOMHighResTimeStamp ;
loadTime readonly attribute unsigned long ;
size readonly attribute DOMString ;
id readonly attribute DOMString ;
url readonly attribute Element ?; [
element Default ]object (); };
toJSON
Each LargestContentfulPaint
object has these associated concepts:
-
A renderTime, initially set to 0.
-
A size, initially set to 0.
-
A loadTime, initially set to 0.
-
An id, initially set to the empty string.
-
A url, initially set to the empty string.
-
An element containing the associated
Element
, initially set to
.null
The entryType
attribute’s getter must return the DOMString
.
The name
attribute’s getter must return the empty string.
The startTime
attribute’s getter must return the value of this's renderTime if it is not 0, and the value of this's loadTime otherwise.
The duration
attribute’s getter must return 0.
The renderTime
attribute must return the value of this's renderTime.
The loadTime
attribute must return the value of this's loadTime.
The size
attribute must return the value of this's size.
The id
attribute must return the value of this's id.
The url
attribute must return the value of this's url.
The element
attribute’s getter must perform the following steps:
-
If this's element is not exposed for paint timing given null, return null.
Note: The above algorithm defines that an element that is no longer a descendant of the Document
will no longer be returned by element
's attribute getter, including elements that are inside a shadow DOM.
This specification also extends Document
by adding to it a largest contentful paint size concept, initially set to 0.
It also adds an associated content set, which is initially an empty set. The content set will be filled with (Element
, Request
) tuples. This is used for performance, to enable the algorithm to only consider each content once.
Note: The user agent needs to maintain the content set so that removed content does not introduce memory leaks. In particular, it can tie the lifetime of the tuples to weak pointers to the Elements
so that it can be cleaned up sometime after the Elements
are removed. Since the set is not exposed to web developers, this does not expose garbage collection timing.
4. Processing model
Each Window
has has dispatched scroll event, a boolean which is initially set to false.
4.1. Modifications to the DOM specification
This section will be removed once the [DOM] specification has been modified.
Right after step 1, we add the following step:
-
If target’s relevant global object is a
Window
object, event’stype
isscroll
and itsisTrusted
is true, set target’s relevant global object's has dispatched scroll event to true.
4.2. Report Largest Contentful Paint
Document
document, a timestamp now, an ordered set of pending image records paintedImages, and an ordered set of elements paintedTextNodes, perform the following steps:
-
For each record of paintedImages:
-
Let imageElement be record’s element.
-
If imageElement is not exposed for paint timing, given document, continue.
-
Let request be record’s request.
-
Let candidate be (imageElement, request)
-
Let intersectionRect be the value returned by the intersection rect algorithm using imageElement as the target and viewport as the root.
-
Potentially add a LargestContentfulPaint entry with candidate, intersectionRect, now, record’s loadTime and document.
-
-
For each textNode of paintedTextNodes,
-
If textNode is not exposed for paint timing, given document, continue.
-
Let candidate be (textNode, null)
-
Let intersectionRect be an empty rectangle.
-
For each
Text
node text of textNode’s set of owned text nodes:-
Augment intersectionRect to be smallest rectangle containing the border box of text and intersectionRect.
-
-
Intersect intersectionRect with the visual viewport.
-
Potentially add a LargestContentfulPaint entry with candidate, intersectionRect, now, 0, and document.
-
4.3. Determine the effective visual size of an element
In order to determine the effective visual size of an Element, run the following steps:
- Input
-
intersectionRect, a
DOMRectReadOnly
imageRequest, a
Request
element, an Element
document, a Document
- Output
-
The size to report for Largest Contentful Paint, in pixels, or null if the element should not be an LCP candidate.
-
Let width be intersectionRect’s
width
. -
Let height be intersectionRect’s
height
. -
Let size be
width
.* height -
Let root be document’s browsing context’s top-level browsing context’s active document.
-
Let rootWidth be root’s visual viewport’s width, excluding any scrollbars.
-
Let rootHeight be root’s visual viewport’s height, excluding any scrollbars.
-
If size is equal to rootWidth times rootHeight, return null.
-
If imageRequest is not eligible to be largest contentful paint, return null.
-
If imageRequest is not null, run the following steps to adjust for image position and upscaling:
-
Let concreteDimensions be imageRequest’s concrete object size within element.
-
Let visibleDimensions be concreteDimensions, adjusted for positioning by object-position or background-position and element’s content box.
Note: some of those algorithms are not rigorously defined in CSS. The expected result is to get the actual position and size of the image in element as a
DOMRectReadOnly
.-
Let clientContentRect be the smallest
DOMRectReadOnly
containing visibleDimensions with element’s transforms applied. -
Let intersectingClientContentRect be the intersection of clientContentRect with intersectionRect.
-
Set size to
intersectingClientContentRect’s
.width
* intersectingClientContentRect’sheight
Note: this ensures that we only intersect with the image itself and not with the element’s decorations.
-
Let naturalArea be
imageRequest’s natural width
.* imageRequest’s natural height -
If naturalArea is 0, then return null.
-
Let boundingClientArea be
clientContentRect’s
.width
* clientContentRect’sheight
-
Let scaleFactor be
boundingClientArea
./ naturalArea -
If scaleFactor is greater than 1, then divide size by scaleFactor.
-
-
Return size.
-
4.4. Potentially add LargestContentfulPaint entry
Note: A user agent implementing the Largest Contentful Paint API would need to include
in supportedEntryTypes
for Window
contexts.
This allows developers to detect support for the API.
In order to potentially add a LargestContentfulPaint
entry, the user agent must run the following steps:
- Input
-
candidate, a largest contentful paint candidate
intersectionRect, a
DOMRectReadOnly
renderTime, a DOMHighResTimestamp
loadTime, a DOMHighResTimestamp
document, a Document
- Output
-
None
-
If document’s content set contains candidate, return.
-
Append candidate to document’s content set
-
Let window be document’s relevant global object.
-
If either of window’s has dispatched scroll event or has dispatched input event is true, return.
-
Let size be the effective visual size of candidate’s element given intersectionRect.
-
If size is less than or equal to document’s largest contentful paint size, return.
-
Let url be the empty string.
-
If candidate’s request is not null, set url to be candidate’s request's request URL.
-
Let id be candidate’s element's element id.
-
Let contentInfo be a map with contentInfo["size"] = size, contentInfo["url"] = url, contentInfo["id"] = id, contentInfo["renderTime"] = renderTime, contentInfo["loadTime"] = loadTime, and contentInfo["element"] = candidate’s element.
-
Create a LargestContentfulPaint entry with contentInfo, and document as inputs.
-
4.5. Create a LargestContentfulPaint entry
In order to create a LargestContentfulPaint
entry, the user agent must run the following steps:
- Input
-
contentInfo, a map
document, a
Document
- Output
-
None
-
Set document’s largest contentful paint size to contentInfo["size"].
-
Let entry be a new
LargestContentfulPaint
entry with document’s relevant realm, with its -
Queue the PerformanceEntry entry.
-
5. Security & privacy considerations
This API relies on Paint Timing for its underlying primitives. Unlike the similar API Element Timing, LCP may expose timing details of some elements with small sizes, if they are still the largest elements to be painted up until that point in the page’s loading. That does not seem to expose any sensitive information beyond what Element Timing already enables.