First -screen-paint, if you come, look forward to leaving your star ~

Recognize a few concepts

1. First Paint(FP)

First Paint is defined as the process by which the rendering tree transforms into screen pixels for the First time. We use FP time to express the First rendering time. FP time can also be interpreted as white screen time if the screen we see before FP is blank. How do you calculate that?

if (window.performance) {
    let pf = window.performance;
    let pfEntries = pf.getEntriesByType('paint')
    let fp = pfEntries.find(each= > each.name === 'first-paint')
    console.log('first paint time: ', fp && fp.startTime)
}
Copy the code

2. First Contentful Paint(FCP):

FCPDefines the process of loading the page onto the screen for the first time with rendered content, which can be text, images,svgElement and non-whitecanvasElements. In the loading timeline below, Figure 2 isFCPPoint in time:We useFCP timeTo express when the content was first rendered. How do you calculate that?

if (window.performance) {
    let pf = window.performance;
    let pfEntries = pf.getEntriesByType('paint')
    let fp = pfEntries.find(each= > each.name === 'first-contentful-paint')
    console.log('first paint time: ', fp && fp.startTime)
}
Copy the code

It needs to be distinguished from FP, there is always FP time ≤ FCP time.

3. First Meaningful Paint(FMP)

FMPThe definition of “main content” depends on the implementation details in each browser, so it is not used as a standardized metric. In the Chrome Lighthouse panel you can see this indicator:

4. Largest Contentful Paint(LCP)

FMPIs hard to define, butLCPThe range is constant and defines the process by which the page starts to load to render out the maximum content (text, images, etc.). Loading timeline as shown below:

In the first example, the Instagram logo is the largest chunk of content in the viewport, and in the second example, the green text is the largest chunk of content in the viewport. We useLCP timeTo express the maximum content rendering time, how to calculate?

new PerformanceObserver(list= > {
    let entries = list.getEntriesByType('largest-contentful-paint');
    entries.forEach(item= > {
        console.log('largest contentful pain time: ', item.startTime)
    })
}).observe({ entryTypes: ['largest-contentful-paint']});Copy the code

What is a first screen rendering?

The first screen is defined as the process from the start of loading to the completion of rendering of the first screen of Windows without scrolling. In this article, we will call the first screen paint Time (FSP time).

Count the first screen rendering time

Let’s start with the simplest scenario: our page is purely static text, meaning there are no images on the front screen and the content is static text.

We need to solve a problem: how do we define which elements are inside the screen?

1. getBoundingClientRect

GetBoundingClientRect used to get an element relative to the position of the window, in theory we just calculate the location of each element, combined with the height of the window information, we will be able to determine whether the element belongs to the screen.

But in real life, the number of DOM on a page is very large, and a large number of DOM operations can affect the performance of the entire page! In addition, getBoundingClientRect causes what Forces Reflow /layout, which is not an ideal solution;

2. IntersectionObserver + MutationObserver

IntersectionObserver starts an observer to check whether the target element appears in the viewport in an asynchronous manner. The returned data contains two important information: IntersectionObserver IntersectionObserver

  • Time: the time at which the element’s visibility changes, a high-precision timestamp in milliseconds;
  • IntersectionRatio: intersectionRatio of target elements is in the range of 0.0 to 1.0. A value of 0 indicates that the element is invisible, and a value of 1 indicates that the element is completely visible.

We then need to add a Intersection observer to each element. The MutationObserver provides the ability to monitor changes to the DOM tree. We use it to monitor changes to the subtrees of the document root node. To register a IntersectionObserver for each newly added child node, refer to the following code:

// Register visibility listeners
const isObserver = new IntersectionObserver((entries) = > {
    entries.forEach((entry) = > {
        // Screen elements
        if (entry.intersectionRatio > 0) {
            // Record the node and its time. This can also be done manually: performance.now()
            console.log(`${entry.target}: ${entry.time}`); }}); });// Register the DOM tree change listener
const muObserver = new MutationObserver((mutations) = > {
    if(! mutations)return;
    mutations.forEach((mu) = > {
        if(! mu.addedNodes || ! mu.addedNodes.length)return;
        mu.addedNodes.forEach((ele) = > {
            // Only element nodes are listened on
            if (ele.nodeType === 1) {
                // Add visibility change listenersisObserver.observe(ele); }}); }); });// Listen for changes in the document subtree
muObserver.observe(document, {
    childList: true.subtree: true
});
Copy the code

For more complete code reference:first-screen-paint

Scenario 2: The first screen contains an image resource, possibly an image element or a background, and the time required to load the slowest image resource is calculated

Problem 1: Image resources are loaded asynchronously. How to obtain the request time of resources?

PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver: PerformanceObserver

  • Name: resource URL;
  • InitiatorType: resource types, values may be CSS | img | xmlhttprequest, etc.;
  • StartTime: request startTime, high-precision timestamp value, in milliseconds;
  • ResponseEnd: indicates the return time of the request response, in milliseconds;
  • Duration:responseEndinstartTimeThe difference between the value;
const pfObserver = new PerformanceObserver((list) = > {
    const entries = list.getEntriesByType('resource');
    entries.forEach((item) = > {
        // Time consuming of various resources
        // First screen image whitelist: imgUrlWhiteList = []
        console.log(`${item.name: ${item.duration}}`);
    });
});
// Set performance listening category: resources
pfObserver.observe({ entryTypes: ['resource']});Copy the code

Question 2: In the above code, we listen for all resource requests. How to retrieve the image resource request on the first screen?

  1. For the image resources of the IMG tag, we can use theMutationObserverorIntersectionObserverDom reads are handled directly in the listenerimgthesrcordata-srcProperty to save the image URL;
  2. For the background image, we use the getComputedStyle method to get the node’s style sheet and extract itbackground-imageThe value of the;

Scenario 3: The first screen content is the dynamic fetch, or even the fetch is the picture resource, just like the home page of the mall?

Data is fetch dynamically. If it is plain text data, there is no image resource. Our DOM tree change listener can listen to the rendering situation after the data is returned. The rendering process will collect the visibility change time of these nodes (this time must be after the fetch data return time). If you render an image resource, you enter the scene from the previous image resource.

Two questions

1. The first screen is still loading and the user triggered the page scroll?

After the page scrolls, the contents of the second screen appear in the window, while the contents of the first screen (some of which may not have been rendered) are not in the window. Then, using the above statistics, the rendering time of the current window content is counted, which may be an “error”.

We need a consensus that scrolling is triggered before the first screen content is fully rendered, indicating that the page is already in an interactive state. In this case, we believe that the content of the first screen frame when the user triggers scrolling is already acceptable to both the user and the developer. Based on this premise, our approach is as follows:

When the page is rolling, add a lock to stop listening to subsequent content changes, take the time point of initial rolling as the time boundary, count the time consuming of all resource requests issued before this point (according to startTime) and the rendering time of DOM tree nodes;

2. In Scenario 3, the first screen is not fully loaded, and the user triggers page scrolling?

  • In this case, only the end time of the fetch request can be guaranteed.
  • If the user triggers scrolling before the response, and data rendering has not started at this time, our program cannot capture the DOM node, and then we cannot get the image resources of the response, so we cannot count the subsequent rendering time.
  • If the scrolling is triggered by the user after the data is returned and before the image resource is rendered, in this case, we can theoretically obtain the loading time of the response image resource because we can capture the rendering of the DOM tree node.

Test the

In this test demo, the main content of the page is the IMG element, as followsLCPDefinition of Lagest Contentful Paint,LCP timeReturns the render time of the image; And our first screen content is also this picture, so ourFSP timeIt should be basically equal toLCP timeIn the screenshot below, this is also basically verified!

Finally, if you have any comments on the above questions, please leave them in the comments section

Warehouse address: first-screen-paint, welcome to issue ~

Reference:

  • web.dev/fcp/
  • Web. Dev/first – meani…
  • web.dev/lcp/