What is FMP?

Perhaps you are familiar with the term “white screen time”, it is the “slash-and-burn” era, one of the page performance metrics we collected, with the complexity of front-end engineering, white screen time has no substantive meaning, replaced by FMP.

Let’s start with a few related terms.

  • FP (First Paint) : The First Paint, which marks the point at which the browser renders any content that is visually different from the content on the screen before navigation
  • FCP (First Contentful Paint) : The First content painting, which marks the point in time when the browser renders the First DOM of content, which may be text, image, SVG, or<canvas>Elements such as
  • FMP (First Meaning Paint) : Marks the time when the main character element is rendered. The main character element can be a video control of a video site, a page frame of a content site, or a header image of a resource site.

Compared to FP and FCP, FMP is the most important performance metric that our front end focuses on, and Google defines it as “Useful?” Time point. However, the question “does it work?” Is difficult to define in a general way, so there is still no standard API output.

There are several ways in the community to calculate “relatively accurate” FMP, relative to actual projects.

  1. Active reporting: the developer reports the time in the “Meaning” position of the corresponding page
  2. Weight calculation: Calculates the render time of the element with the highest weight according to the page element
  3. Trend calculation: During render, FMP values are calculated based on dom trends

This article will focus on the second approach.

Weighting positioning

The so-called weight refers to traversing one or a group of DOM with the largest “weight value” of the elements of the page with the agreed “weight ratio”, and then using its “loading time point” or “loading end point” as the MAPPING of FMP.

weighting

Node tag

To stage markup of a DOM node, you need the ability to listen for changes in the DOM, and thankfully HTML5 gives you that ability.

MutationObserver, a replacement for the Mutation Events feature, is part of the DOM3 Events specification. He can perform the callback when the specified DOM changes.

MutationObserver has three methods

  • disconnect()

    Prevent the MutationObserver instance from receiving any further notifications until its observe() method is called again, and the observer object’s contained callback function is not called again.

  • observe()

    Configure the MutationObserver to start receiving notifications through its callback function when a DOM change matches a given option.

  • takeRecords()

    Remove all pending notifications from the notification queue of the MutationObserver and return them to a new Array of the MutationRecord object.

global.mo = new MutationObserver((a)= > { 
    /* Callback: DOM node sets the stage flag */
});

/** * mutationObserver.observe(target[, options]) * target - The DOM Node that needs to observe changes. * options-mutationObServerInit object that configures the changes to be observed. * the introduction of more options please refer to https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit#%E5%B1%9E%E6%80%A7 * * /
global.mo.observe(document, {
  childList: true.// Listen for child changes (if subtree is true, include child nodes)
  subtree: true // All nodes of the entire subtree
});
Copy the code

The following coarse filter interprets a normal single page rendering process

  • Preparatory stage: the navigation stage, in the process of connecting corresponding
  • Stage 1: The first byte rendering stage, which is also the first effective change to the FCP DOM tree
  • Stage 2: Basic frame rendering completed
  • Stage 3: Get the data and render it to the view
  • Stage 4: Image loading is complete and the loading process is not marked

In fact, there were a lot of DOM changes between the first and third phases, and Mutation Observer events were not triggered synchronously, but asynchronously, meaning that they were not triggered until all DOM operations in the current “phase” had finished.

The Mutation Observer has the following characteristics

  • It waits for all script tasks to complete before running (that is, asynchronously triggered).
  • Instead of processing DOM changes individually, it wraps DOM changes into an array for processing.
  • It can either observe all types of changes to the DOM, or specify that only one type of change is observed.

After the load event is triggered, the tags for each phase are already attached to the tag

Here we mark key with “_ti” last night.

Note down the current time when marking the time for later use

/ / pseudo code
function callback() {
    global.timeStack[++_ti] = performance.now(); / / time
    doTag(_ti); / / to play tag
}
Copy the code

After the tag is finished, we wait for the moment of load to calculate and push back.

Calculate weight value

In general

  • The larger the view proportion, the more likely it is to be the main element
  • Videos are more likely to be the main character than pictures
  • svgcanvasIt is also important
  • The rest of the elements can be evaluated as normal DOM
  • Background pictures are optional
Step 1: Simple and rough, by size
/ / pseudo code
function weightCompute(node){
    let {
        width,
        height,
        left,
        top
    } = node.getBoundingClientRect();
    
    // Exclude elements out of view
    if(isOutside(width, height, left, top)){
        return 0;
    }
    let wts = TAG_WEIGHT_MAP[node.tagName]; // The specified weight ratio
    let weight = width * height * wts; WTS (width, height, WTS);
    return {
        weight, 
        wts, 
        tagName: node.tagName, 
        ti: node.getAttribute("_ti"),
        node
    };
}
Copy the code
Step 2: Deduce the hero element from the weight values

Under our convention weight algorithm, the element with the highest weight is the hero element we push to.

/ / pseudo code
function getCoreNode(node){
    let list = nodeTraversal(node); // Recursively calculate the weight value of each labeled node
    return getNodeWithMaxWeight(list); // weight is the element with the largest weight
}
Copy the code
Step 3: Take the time based on the element type

Different elements get time in different ways

  • Common element: calculated by marking point time
  • Pictures and videos: based on the end time of the resource
  • Background elements: Can be calculated by the corresponding end time of the background resource, or by ordinary elements
/ / pseudo code
function getFMP(){
    let coreObj = getCoreNode(document.body),
        fmp = - 1;
    let {
        tagName,
        ti,
        node
    } = coreObj;
    
    switch(tagName){
        case 'IMG':
        case 'VIDEO':
            let source = node.src;
            let { responseEnd } = performance.getEntries().find(item= > item.name === source);
            fmp = responseEnd || - 1;
            break;
        default:
            if(node.style.backgroundImage){
                // Background processing for common elements
            }else{ fmp = global.timeStack[+ti]; }}return fmp;
}
Copy the code

Return to validate

For our demo page, for a similar e-commerce site, we want to get “stage 2” or “Stage 3” time points as our FMP values.

Since we do not want the background of the “hero element” or the corresponding time of the “picture hero element” to be counted in the value of FMP, we relegate resource elements such as “picture” and “video” to ordinary element calculation.

In Chrome [Disable cache/Fast 3G] we do simulation verification.

The calculated FMP value is 4730.7ms, and the Chrome Performance monitoring value is around 4950ms, with an error of about 200ms.

If the speed limit is relaxed, the value of FMP will be closer to the “First Meaning Paint” we want.

Please indicate the source for reprinting

By Zwwill Kiba

First address: Zwwill /blog#32