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):
FCP
Defines the process of loading the page onto the screen for the first time with rendered content, which can be text, images,svg
Element and non-whitecanvas
Elements. In the loading timeline below, Figure 2 isFCP
Point in time:We useFCP time
To 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)
FMP
The 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)
FMP
Is hard to define, butLCP
The 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 time
To 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:
responseEnd
instartTime
The 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?
- For the image resources of the IMG tag, we can use the
MutationObserver
orIntersectionObserver
Dom reads are handled directly in the listenerimg
thesrc
ordata-src
Property to save the image URL; - For the background image, we use the getComputedStyle method to get the node’s style sheet and extract it
background-image
The 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 followsLCP
Definition of Lagest Contentful Paint,LCP time
Returns the render time of the image; And our first screen content is also this picture, so ourFSP time
It should be basically equal toLCP time
In 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/