As mentioned in the previous chapter, the weight value locates the performance indicator FMP. As for how to calculate the weight, it is not very clear. This chapter will discuss how to calculate the weight value “relatively accurately” and how to screen out the FMP value we want.

The following content is “slightly lighter weight”

How to Monitor nodes

Monitor the changes

MutationObserver

One sentence explanation

“MutationObserver gives us the ability to capture” facets “of DOM rendering.

The “MDN Explain” MutationObserver interface provides the ability to monitor changes made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which is part of the DOM3 Events specification.

For more details, see developer.mozilla.org/zh-CN/docs/…

Node tag

With the above capabilities, you can both listen and “tag” nodes

Like this,

/ / pseudo code
new MutationObserver((a)= > {
    let timestamp = performance.now() || (Date.now() - START_TIME),
    doTag(document.body, global.paintTag++);
    global.ptCollector.push(timestamp);
});
Copy the code

Noun explanation:

  • PaintTag: The dot “_pi” corresponding to the DOM, marking the number of times the render was produced.

  • PtCollector: A collection of time nodes corresponding to paintTag. You can use paintTag to retrieve a time node for a particular render time.

When will it be calculated?

Window.load starts counting

Why is that?

We think that, typically, the window triggers the load event, which means that 90% of the resources and DOM of the main business are ready. The DOM with high weight score calculated at this point is the key FMP node we want to find.

I don’t care how you render it, asynchronously or straight out, all I care about is the result, okay

How do I filter elements?

Calculate weight score

Based on the node

Calculation method of weight score of a base node (no child node) :

/ / pseudo code
const TAG_WEIGHT_MAP = {
    SVG: 2.IMG: 2.CANVAS: 2.VIDEO: 4
};

node= > {
    let weight = TAG_WEIGHT_MAP[node.tagName],
        areaPercent = global.calculateShowPercent(node);
          
    let score = width * height * weight * areaPercent;
    return score;
}
Copy the code

CalculateShowPercent is explained in the following figure

The parent node

This is an algorithm that I call “surrogate father campaigning.”

Scoring method to calculate the weights of the parent node itself with the base node is the same, the difference is that if his son node score and score is greater than or equal to its own, will be used by children group instead of parent nodes to participate in more advanced, at the same time, the child nodes are weighted score and score as the parent node, on the other hand, if the child node is a grandson of nodes represent, grandson node will be synchronized to upgrade.

How do you understand that?

The following two situations:

one

Parent element score =400 * 100 = 40000The child element scores and =300 * 60 + 60 * 60 = 21600Parent element score > child element score andCopy the code

In this case, this group of elements with a score of 40,000 to enter the next level of election. The list of candidates is the parent element itself.

The data structure is as follows:

{deeplink: [{...}], node: parent#id_search, score: 40000}Copy the code

two

Parent element score =400 * 300 = 120000The child element scores and =400 * 300 + 60 * 100 = 126000Parent element score < child element score sumCopy the code

In this case, the group should advance to the next level with a score of 126000. The list of elements that run is the child element group, “run for the parent”.

The data structure is as follows:

{deeplink: [{...}], elements: [{node: child# id_slides_pics,...}, {node: child# id_slides_index,...}], the node: parent#id_slides, paintIndex: 2, score: 126000 }Copy the code

It can be inferred from the above two cases

Parent element score =400 * 400 = 160000The child element scores and =40000 + 126000 = 166000Parent element score < child element score and one of the child nodes are represented by the grandchildrenCopy the code

= = >

{
    deeplink: [{…}],
    elements: [
        {node: child#id_search, ...},
        {node: child#id_slides_pics, ...},
        {node: child#id_slides_index, ...}
    ],
    node: parent#id_body,
    paintIndex: 1,
    score: 166000
}
Copy the code

Therefore, the following combination and split is not difficult to understand.

Elimination of distractions

In the process of document depth traversal calculation, we always encounter some interference factors to make our script calculation error, the following two are the most common

Invisible element

This element, though invisible to the user, can have a significant impact on the outcome of the election.

Treatment scheme
const isIgnoreDom = node= > {
    return getStyle(node, 'opacity') < 0.1 ||
        getStyle(node, 'visibility') = = ='hidden' ||
        getStyle(node, 'display') = = ='none' ||
        node.children.length === 0 &&
        getStyle(node, 'background-image') = = ='none' &&
        getStyle(node, 'background-color') = = ='rgba(0, 0, 0, 0)';
}
Copy the code

First, we believe that elements with **opacity < 0.1 visibility === ‘hidden’ and display === ‘none’ are invisible elements and should be ignored. In addition, elements without child nodes, background and color are also invisible elements and should be ignored.

Scroll to offset

Since our script is executed after Window Load, most of the time the browser scroll bar has already been offset. Errors may occur in the selection results. The diagram below:

At this time, the selection result is

<div class="channel" _pi="30">.</div>
Copy the code

When _pi goes to 30, the “30th render”, no matter how fast, this value is always much higher than the actual FMP.

There are two things that can cause a “roll offset.

  1. inloadIt’s not uncommon for the user to actively browse before the trigger is triggered, and the user can’t wait to load every time. And if there ispendingThe load of resources will be very late.
  2. load“ScrollRestore” was executed before the browser triggered.

In the second case, still very good solution, because not all browsers have History. ScrollRestoration effects, so we just need to turn off, but that one is we can’t control, no matter what.

Therefore, there is only another way to “delimit the calculation area”, and this area should avoid the influence of the scroll bar position.

Treatment scheme

Of course, we also have a method, in fact, very simple.

This is because “The width and height of the Document object are fixed and the offset is synchronized with the scroll bar.”

const getDomBounding = dom= > {
    const { x, y } = document.body.getBoundingClientRect();
    const { left, right, top, bottom, width, height } = dom.getBoundingClientRect();
    return {
        left: left - x,
        right: right - x,
        top: top - y,
        bottom: bottom - y,
        height, width
    }
}
Copy the code

If there is any omission above, please kindly comment, and we would be grateful! 🤝

Different elements have different FMP algorithms

Common elements

For common elements like

, ,

, , the labeled _pi value is indexed to the render time of the time node ptCollector remember? This time can be used as the FMP value.

In particular, if a normal element has a background image, it is upgraded to an resource element

Resource elements

For example, ,
, the responseEnd time node of this element’s resource will be used as the FMP value

However, we can rationalize the TAG_WEIGHT_MAP global weight configuration for different projects. Of course, you can also ignore resource elements such as “images” and “videos” and the load time of resources depends on the actual project


Starting: zwwill/blog# 34

Author: Kiwa

Please indicate the source for reprinting