In the past, most tools only used loading performance as a web performance metric, but poor performance can happen at any time, not just during loading. Applications that don't respond quickly to clicks, don't scroll smoothly, or have stuttering animations can all lead to a poor experience. Users care about the overall experience, and traditional performance metrics such as load time or DOMContentLoaded time are highly unreliable because the time at which a load occurs may or may not correspond to what the user thinks the application is loading.

The following questions need to be thought about.

  • What metrics provide the most accurate measure of human perceived performance?

  • How can these metrics be measured against real users?

  • How do you interpret the measurements to determine if the application is "fast"?

  • How can we avoid performance degradation and improve performance in the future once we know the actual user performance of the application?

User-centric performance metrics

When users navigate to a web page, they are usually the first to look for visual feedback.

Does it happen? Did the navigation start successfully? Is the server responding?
Does it work? Has enough content been rendered that can be interacted with by the user?
Is it available? Can the user interact with the page, or is the page still busy loading?
Is it enjoyable? Are the interactions smooth and natural, without lags and jams?
First Plot (FP) and First Content Plot (FCP)

The Paint Timing API defines two metrics: First Paint (FP:First Paint) and First Contentful Paint (FCP:First Contentful Paint), which are used to mark the point in time when the browser renders pixels on the screen after the page loads. This is important to users because it answers the question: Did it happen?

Difference between FP and FCP: FP marks the point in time when the browser renders anything visually different from the content on the screen before navigation, while FCP marks the point in time when the browser renders the first content from the DOM, which could be text, images, SVG or even <canvas> elements.

First effective drawing and lead element timing (FMP)

The First Meaningful Paint (FMP: First Meaningful Paint) metric answers the question "Does it work?" . On a web page, there is almost always one part of the content that is more important than the rest. If the most important part of the page loads quickly, the user may not notice if the rest of the page loads.

Longer tasks (long task)

The browser responds to user input by adding tasks to the queue of the main thread to wait to be executed one by one. The browser also does this when executing JavaScript, so from this perspective, the browser is single-threaded. If a task takes a long time, the main thread is blocked, and all other tasks in the queue must wait. The Long Tasks API can flag any task that takes longer than 50 milliseconds as a possible problem, and the 50 milliseconds was chosen so that the application can meet the requirement of responding to user input within 100 milliseconds, as described in the Web Performance Evaluation Model

Time to interact (TTI)

The Time to Interact (TTI) metric is used to mark the point at which the application has been visually rendered and can reliably respond to user input. Applications may fail to respond to user input for a number of reasons.

  • The JavaScript required for the page component to run has not been loaded.

  • Time-consuming tasks blocking the main thread

The TTI metric identifies the point in time when the initial JavaScript on the page is loaded and the main thread is idle (no time-consuming tasks).

Other indicators

  • LCP (Largest Contentful Paint) Maximum Contentful Rendering

represents the time to load the largest page element in the viewport. The LCP data is recorded through the PerformanceEntry object, and a new PerformanceEntry object is created each time a larger content is rendered.

  • TBT (Total Blocking Time) page blocking time

TBT aggregates the duration of all blocking user operations during loading, and the blocking part of any long task between FCP and TTI is aggregated.

  • SI (Speed Index)

This indicator shows the display speed of the visible part of the page, in time,

Metrics correspond to visual feedback

Experience Indicators
Does it happen? First time plotting (FP)/first time content plotting (FCP)
Does it work? First effective drawing (FMP)/lead element timing
Is it available? Time to interact (TTI)
Is it enjoyable? Time-consuming tasks (there are no technically time-consuming tasks)
Metrics

Before the api was available, the following code was used to detect standing tasks

(function detectLongFrame() {
  var lastFrameTime = Date.now();
  requestAnimationFrame(function() {
    var currentFrameTime = Date.now();

    if (currentFrameTime - lastFrameTime > 50) {
      // Record long tasks
    }

    detectLongFrame(currentFrameTime);
  });
}());

While this code works in most cases, it has a number of drawbacks.
1. This code adds overhead to each frame.
2. This code will block idle blocks.

3. This code can be a serious drain on battery life.

The most important rule for performance measurement code is that it should not degrade performance.

Thanks to several new browser APIs, it is possible to measure these metrics on real devices without having to use a lot of workarounds that may degrade performance. These new APIs are PerformanceObserver, PerformanceEntry, and DOMHighResTimeStamp.

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.entryType);
    console.log(entry.startTime); // DOMHighResTimeStamp
    console.log(entry.duration); // DOMHighResTimeStamp
  }
});

observer.observe({entryTypes: ['resource', 'paint']});

The PerformanceObserver provides us with the ability to subscribe to performance events as they occur and respond to events asynchronously.

Measuring FP and FCP

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // `name` may be 'first-paint' or 'first-contentful-paint'.
      const metricName = entry.name;
      const time = Math.round(entry.startTime + entry.duration);

      console.log({
        eventCategory:'Performance Metrics',
        eventAction: metricName,
        eventValue: time,
        nonInteraction: true,
      });
    }
});
observer.observe({entryTypes: ['paint']});

// or

performance.getEntriesByType('paint')

Note: You must ensure that the PerformanceObserver is registered in the <head> of the document prior to any stylesheet in order for it to run before FP/FCP occurs.

Measuring FMP (no criteria for this metric at this time)

Once the lead elements on the page are identified, the point in time at which they are presented to the user can be tracked. There is no standardized definition of FMP and therefore no performance entry type.

Measuring TTI

TTIs can be detected using TTI Polyfill, which exposes the getFirstConsistentlyInteractive() method and returns a promise that uses TTI values for parsing.

import ttiPolyfill from './path/to/tti-polyfill.js';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
  console.log({
    eventCategory:'Performance Metrics',
    eventAction:'TTI',
    eventValue: tti,
    nonInteraction: true,
  });
});

Measuring input latency

Longer tasks that block the main thread may cause event listeners to fail to execute in a timely manner. To provide a smooth interface experience, the interface should respond within 100 milliseconds of the user's execution of input.

To detect input delays in your code, compare the event timestamp to the current time and determine if the difference is more than 100 milliseconds.

const subscribeBtn = document.querySelector('#subscribe');

subscribeBtn.addEventListener('click', (event) => {
  // Event handling logic​

  const lag = performance.now() - event.timeStamp;
  if (lag > 100) {
    console.log({
      eventCategory: 'Performance Metric'
      eventAction: 'input-latency',
      eventLabel: '#subscribe:click',
      eventValue: Math.round(lag),
      nonInteraction: true,
    });
  }
});
Since event delays are usually caused by time-consuming tasks, the event delay detection logic can be combined with the time-consuming task detection logic: determine that a time-consuming task is blocking the main thread at the time indicated by event.timeStamp.

Optimization Strategies

  • Optimization FP/FCP

Removing any scripts or stylesheets from the <head> of a document that block rendering can reduce the wait time before the first draw and the first content draw.

Identify the smallest style set and inline it into the <head> (or use an HTTP/2 server push) to achieve a very short first draw time.

  • Optimization FMP/TTI
After identifying the most critical interface elements (lead elements) on the page, you should ensure that the initial script load contains only the code needed to render these elements and make them interactable.

Avoid time-consuming tasks

Breaking up code and prioritizing what to load not only shortens page interactivity, but also reduces time-consuming tasks.

In addition to splitting code into multiple individual files, large synchronous blocks of code can be split into smaller chunks to be executed asynchronously or deferred to the next free point. Executing this logic in smaller blocks asynchronously allows space in the main thread for the browser to respond to user input.