Performance metrics are key to understanding and improving the web user experience.

Key performance indicators include:

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • First Input Delay (FID)
  • Time to Interactive (TTI)
  • Total Blocking Time (TBT)
  • Cumulative Layout Shift (CLS)

We’ll go through them one by one.

First Contentful Paint(FCP)

First Contentful Paint(FCP) is an important metric used to measure perceived loading speed, which is visually visible to the user. It marks the first point in the page load timeline where the user can see the page appear.

What is the FCP

The FCP measures the time it takes from the time a page is loaded until its content is fully displayed on the screen. Page content here refers to text, images (including background images), < SVG > elements, and non-blank

elements.

In the loading timeline above, FCP appears in frame 2 as the first text and image appear on the screen.

Notice that only part of the screen is rendered at this point, not all of it. This is an important difference between FCP and LCP.

How is FCP measured

FCP can be measured in both development and production environments, each of which has its own measurement tool.

The production environment

PageSpeed Insights

Chrome User Experience Report

Search Console (Speed Report)

web-vitals JavaScript library

The development environment

Lighthouse

Chrome DevTools

Measure FCP using JavaScript

FCP can be measured using Paint Timing.

new PerformanceObserver((entryList) = > {
  for (const entry of entryList.getEntriesByName('first-contentful-paint')) {
    console.log('FCP candidate:', entry.startTime, entry);
  }
}).observe({type: 'paint'.buffered: true});
Copy the code

Here we create a PerformanceObserver to listen for first-Contentful-paint.

Here you just print first-Contentful-Paint Entry. The actual situation is much more complicated than this, which will be explained in detail below.

In the example above, the printed first-Contentful-paint Entry tells us when the first element appears on the screen, but in some cases this entry is inaccurate.

The data obtained through the Paint Timing API is different from the actual data. It is manifested in the following three aspects:

  • The API dispatches one to a page that is loading Tabs in the backgroundfirst-contentful-paintEntry, but these pages are actually ignored (first draw time should only be considered if the page is in the foreground the entire time).
  • The API does not dispatch to pages recovered from the backward/forward cachefirst-contentful-paintEntries, but should be taken into account because users experience different page visits.
  • The API does not count drawing times from cross-domain IFrames, but to properly measure THE FCP, we should consider all iframes. A child iframe can use the API to report its drawing time to its parent, who will merge it.

Dealing with these nuances when measuring FCP using API is less efficient and we can use the Web-Vitals library to measure FCP without having to deal with these differences ourselves.

import {getFCP} from 'web-vitals';

// Measure and log FCP as soon as it's available.
getFCP(console.log);
Copy the code

In some cases (such as cross-domain IFrames), it is not possible to measure THE FCP through JavaScript. The Web-Vitals library illustrates its limitations.

How to improve FCP

Using Lighthouse’s performance review tool, we can identify opportunities to improve the FCP metrics of web pages. Watch the lists for Opportunities and Diagnostics.

To learn how to improve FCP, stay tuned.

Largest Contentful Paint(LCP)

Largest Contentful Paint(LCP) is an important metric used to measure perceptual loading speed (observable by the user’s naked eye). It marks a point in the page load timeline when the main content of the page has been loaded.

Measuring how fast the main content of a web page loads and is visible to users has always been a challenge for front-end development.

Events like traditional Load or DOMContentLoaded aren’t perfect because they don’t necessarily correspond to what the user sees on the screen. LCP only captures the initial stage of loading. If the page has a startup interface or loading progress display, it is irrelevant to the user at this time.

What is the LCP

LCP represents the render time from the first load to the largest image or text block appearing in the view.

In order to provide a good user experience, we should keep the LCP within 2.5s.

The types of elements considered by LCP include:

  • <img>The element
  • <svg>Elements within the<image>The element
  • <video>Element (using cover image)
  • Load elements of the background image via url()
  • Block-level elements that contain text nodes or other inline text elements

Here are a few examples of LCP.

In the two timelines above, the largest element changes as the content is loaded. In the first example, new content is added to the DOM, which changes the maximum element. In the second example, layout changes and the disappearance of the old Max element from the view change the Max element.

Often, postloaded content is larger than what already exists on the page, but this is not always the case. The next two examples show that the largest element appears before the page is fully loaded.

In the first example, the Instagram logo loads relatively early and is still the biggest element even though the rest of the content is progressively displayed. In the example of Google’s search results page, the largest element is a piece of text that displays before any image or logo has finished loading. Because all the individual images are smaller than this paragraph, it is still the largest element during the entire loading process.

How is the LCP measured

This is the same as FCP and will not be repeated.

Measure LCP using JavaScript

Similar to FCP.

new PerformanceObserver((entryList) = > {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
}).observe({type: 'largest-contentful-paint'.buffered: true});
Copy the code

There is the same problem as with FCP – the measurement is inaccurate and the solution is still recommended to use Web-Vitals

import {getLCP} from 'web-vitals';

// Measure and log LCP as soon as it's available.
getLCP(console.log);
Copy the code

First Input Delay(FID)

First Input Delay(FID) is an important indicator used to measure load response speed. It quantifies the user’s experience when interacting with the page.

What is the FID

FID measures the time from the first time a user interacts with a page (for example, when they click a link, click a button, or use a custom javascript component) to the time when the browser can actually start processing event handlers in response to that interaction.

In order to provide a good user experience, we should keep the FID within 100ms.

The common reason for input latency is that the browser’s main thread is too busy to respond to the user in a timely manner. The most common reason is that the browser is busy processing and executing large JavaScript files.

FID measures only the delay in event processing; it does not take into account event processing time itself or the time the browser takes to update the page UI after the event handler is executed.

Here is a typical Web page loading timeline.

The page makes network requests for resources (most likely CSS and JS files), which will be processed on the main thread after the resources are downloaded. This causes the main thread to be temporarily busy (beige task block).

Long first input delays typically occur between FCP and interaction time TTI.

We noticed that between FCP and TTI, there is a considerable amount of time (including three long tasks), and if the user tries to interact with the page during this time (for example, clicking on a link), there is a delay from the time the browser receives the click event until the main thread can respond.

Consider what happens if the user tries to interact with the page at the beginning of the longest task.

Because the interaction occurs while the browser is working on a task, it must wait until the task is complete before it can respond to the interaction. The waiting time is the user’s FID value on the page. FID is not a fixed value because it is entirely possible for the user to interact with the page early in the task (idle time) when FID is not available.

FID only focuses on input events from discrete operations, such as clicks and keystrokes, and does not consider continuous interactive actions such as scrolling and zooming.

How to measure FID

FID can only be measured in a production environment because it requires real users to interact with the page. Measurement tools in production are the same as other metrics.

Measure FID using JavaScript

Similar to FCP.

new PerformanceObserver((entryList) = > {
  for (const entry of entryList.getEntries()) {
    const delay = entry.processingStart - entry.startTime;
    console.log('FID candidate:', delay, entry);
  }
}).observe({type: 'first-input'.buffered: true});
Copy the code

There is the same problem as with FCP – the measurement is inaccurate and the solution is still recommended to use Web-Vitals

import {getFID} from 'web-vitals';

// Measure and log FID as soon as it's available.
getFID(console.log);
Copy the code

Time to Interactive(TTI)

Time to Interactive(TTI) is an important development environment metric used to measure load response speed. It helps to identify situations where a page appears to be interactive but is not.

What’s TTI

TTI measures the time from the start of a page to the time when the primary child resources are loaded and can respond quickly and reliably to user interactions.

We calculate TTI based on the performance tracking results of the web page, following the following steps:

  • Starting from the FCP
  • Search for silent Windows at least 5 seconds ahead, where silent Windows are defined as no long tasks and no more than two network GET requests running
  • Search backwards for the last long task before the silent window. If no long task is found, FCP is used
  • TTI is the period after the end of the last long task (or FCP if no long task is found) and before the silent window

To provide a good user experience, we should keep the TTI within the 5s range.

In order to provide a good user experience, websites should strive to keep interactions under 5 seconds

In the past, front-end developers have sacrificed TTI metrics for faster page rendering.

Techniques like server-side rendering (SSR) may make a page appear interactive (that is, links and buttons are visible on the screen), but it is not actually interactive because the main thread is blocked, or because the JavaScript code controlling these elements is not loaded.

To avoid this problem, minimize the differences between FCP and TTI. In some cases, there is a noticeable difference, and you can load an animation to tell the user that the page is not yet interactive.

How to measure TTI

TTI is best measured in a development environment, and the best way to do this is with the Lighthouse tool to review pages.

Total Blocking Time(TBT)

Total Blocking Time(TBT) is used to measure the Total Blocking Time between FCP and TTI.

The main thread is considered blocked when there is a long task (one that has run for more than 50ms on the main thread). We say the main thread is blocked because the browser cannot interrupt a task in progress. Therefore, when a user interacts with a page during a long task run, the browser must wait for the task to complete before responding.

If the task is long enough (for example, more than 50 ms), users are likely to notice page loading delays.

The total blocking time for a page is the sum of the blocking time for each long task that occurs between FCP and TTI.

Let’s look at the timeline of the main thread running during page loading.

There are five tasks in total, three of which are long tasks because they take more than 50ms to execute. The figure below shows the blocking time for each long task.

Thus, although the total time spent running tasks on the main thread is 560ms, only 345ms of that time is considered to be blocking time.

Task execution time Total blocking time
Task 1 250ms 200ms
Task 2 90ms 40ms
Task 3 35ms 0ms
Task 4 30ms 0ms
Task 5 155ms 105ms
Total blocking time 345ms

How is TBT measured

Use Chrome DevTools and Lighthouse in your development environment.

Cumulative Layout Shift(CLS)

CLS measures the sum of every unexpected layout shift over the life of a page.

If the position of a visible element changes in the first and second frames, a layout shift occurs. Note that the layout changes only when existing elements change their starting position. If a new element is added to the DOM, or an element changes size, its layout stays the same as long as the change does not cause the starting position of other visible elements to change.

To provide a good user experience, we should keep the CLS in the 0.1 range.

Now let’s see where this 0.1 comes from.

The calculation of CLS

To calculate the layout shift, the browser looks at the size of the viewport and the displacement of unstable elements in the viewport between frames. Layout shift is the product of two parameters related to element displacement:

layout shift score = impact fraction * distance fraction

Impact Fraction represents the effect of unstable elements on the viewport region between two frames.

The impact fraction is the union of the regions occupied by unstable elements in the viewports of the two frames.

In the example above, the elements in the left (previous frame) image occupy half of the viewport, and the elements in the right (later frame) image move down 25% of the viewport height. The red dotted box represents the union of the visible areas of the elements in the two frames, i.e. 75%, so the impact fraction is 0.75.

The other part of the layout movement formula measures how far unstable elements move relative to the viewport. Distance fraction is the ratio of the maximum distance an unstable element can travel (horizontal or vertical) to the maximum viewport size (width or height, whichever is larger).

In the example above, the maximum viewport size is height, and the unstable element moves 25% of the viewport height, so the distance fraction is 0.25.

The resulting layout shift is 0.75 * 0.25 = 0.1875

Layout shifts are not without merit; in fact, many dynamic Web applications often change the starting position of elements on a page. If the layout shift is expected by the user, it does no harm. Shifts in response to user interactions (such as clicking a link, pressing a button, entering a search box, etc.) are generally fine, as long as the changes don’t affect the user’s interactions too much.

Animations and transitions, if done well, are a great way to update the content of a page without overwhelming the user. Sudden movement of content on a page can make for a poor user experience. But the gradual and natural movement of content from one location to another can often help users better understand what is happening and guide them during state changes.

The CSS Transform property allows us to animate elements without changing the layout

  • Use transform: scale() instead of changing the height and width attributes
  • To move elements, avoid changing the top, right, bottom, or left attributes and use transform: Translate () instead.

How do I measure CLS

This is the same as FCP and will not be repeated.

Using JavaScript to measure CLS

Similar to FCP.

let cls = 0;

new PerformanceObserver((entryList) = > {
  for (const entry of entryList.getEntries()) {
    if(! entry.hadRecentInput) { cls += entry.value;console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift'.buffered: true});
Copy the code

There is the same problem as with FCP – the measurement is inaccurate and the solution is still recommended to use Web-Vitals

import {getCLS} from 'web-vitals';

// Measure and log CLS in all situations
// where it needs to be reported.
getCLS(console.log);
Copy the code