Front-end optimization is a very broad topic, and there’s a book out there (I don’t actually have that skill), and there are a lot of books out there. Performance issues with animation and interaction are most noticeable, especially on mobile, where the functionality is low. Due to my experience in mobile development, I pay more attention to this area and as a cutout dog who loves to pick up information from others, I summarize some mature optimization methods of others as follows:

Of course, all optimization is a scene, please choose the best scheme according to the actual scene.

DOM related

DOM is slow by nature, and the following metaphor illustrates this relationship graphically.

Think of DOM and JS (ECMAScript) as islands, each connected by a toll bridge. Every time ECMAScript accesses the DOM, it passes over the bridge and pays a “toll.” The more times you access the DOM, the higher the cost.

The most basic optimization idea is to optimize DOM reads and writes.

Reduce DOM element reads:

Caching DOM references

After retrieving the DOM, cache the reference. Do not retrieve the DOM twice. Many people do not develop good habits when using jQuery. Chain calls are easy to use, but sometimes they fall into the trap of ignoring cached DOM because it is too easy to get.

var render = (function() { // get DOM var domCache = document.querySelector("dom"); return function() { // do something... domCache.style.width = '100px'; domCache.style.height = '100px'; / /... }}) ();Copy the code

Cache DOM attributes

The idea is the same as above. After obtaining the initial value and knowing the change amount, the changed value of the element can be directly calculated and cached in memory, avoiding storing the result value with DOM attribute. You can eliminate a lot of unnecessary DOM reads, especially if certain properties (which are discussed below) cause browser backflow. This is easy to ignore when using JavaScript to control the position of some objects. Back in the jQuery era, people used to store data on DOM elements, but this caused performance problems, and I made a similar mistake that led to poor performance in a racing game on mobile.

// bad var dom = document.querySelector("dom"); var step = 1; var timer = window.setInterval(function () { var left = parseInt(dom.style.left); if (left >= 200) { clearInterval(timer); } dom.style.left = (left +1) + 'px'; }, 1000/60); // good var dom = document.querySelector("dom"); var step = 1; var left = parseInt(dom.style.left); var timer = window.setInterval(function () { if (left >= 200) { clearInterval(timer); } left++; dom.style.left = left + 'px'; }, 1000/60);Copy the code

HTMLCollection also has a very important feature that it is dynamically updated according to the situation of the page. If you update the page, its content will also change. The following code will be infinite loop.

var divs = document.getElementsByTagName("div") ;
for(var i = 0 ; i < divs.length ; i ++){
    document.body.appendChild(document.createElement("div")) ;
}
Copy the code

Reduce DOM writes

Record the last result and the existing result Diff, if there is a change to write operations, remove unnecessary write operations.

var dom = document.querySelector('#dom'); var lastVal = null; var currVal = null; if (lastVal ! == currVal) { dom.style.someAttr = currVal; }Copy the code

Avoid looping over DOM elements

DOM operation in the loop, each loop will generate a read operation and a write operation, so our optimization idea is to cache the loop result, after the loop end unified operation can save a lot of read and write operations.

Merges multiple writes

// bad for (var i = 0; length < 100; I ++) {// get, set document.getelementById ('text').innerhtml += 'text ${I} '} // better var HTML = ''; for (var i = 0; length < 100; i++) { html += `text${i}` } document.getElementById('text').innerHTML = html;Copy the code

usedocumentFragment

In addition, the documentFragment can achieve this purpose, because the documentFragment exists in memory, not in the DOM tree, so inserting child elements into the documentFragment does not cause page backflow. Therefore, the use of document fragments is often used to optimize performance.

var fragment = document.createDocumentFragment();

for (var i = 0; length < 100; i++) {
    var div = document.createElement('div');
    div.innerHTML = i;
    fragment.appendChild(div);
}

document.body.appendChild(fragment)
Copy the code

As for who is faster with innerHTML or fragment, see here. There’s also a new optimization rule: use innerHTML(and even better insertAdjacentHTML) and fragment first.

Reflow vs. Repaint

If you’ve looked at how browsers render, you know that the performance cost of redrawing and reflow can be severe, damaging the user experience and causing the UI to stall. Backflow is also called rearrangement, backflow always causes redraw, redraw does not necessarily trigger backflow. Conditions that trigger browser backflow and redraw are:

  • Add or remove visible DOM elements
  • Element position change
  • Element size change
  • Element content changes
  • Page render initialization
  • Browser window sizes change, font sizes change, pages scroll

Our optimization approach is to reduce or even avoid triggering backflow and redraw in the browser.

Avoid properties that cause browser backflow

The browser also rearranges to get the correct values for attributes such as:

  • Element:
    • offsetTop,offsetLeft,offsetWidth,offsetHeight
    • scrollTop,scrollLeft,scrollWidth,scrollHeight
    • clientTop,clientLeft,clientWidth,clientHeight
  • Frame, HTMLImageElement:
    • height,width
  • Range:
    • getBoundingClientRect().
    • getClientRects()
  • SVGLocatable:

    • computeCTM()
    • getBBox()
  • SVGTextContent:

    • getCharNumAtPosition()
    • getComputedTextLength()
    • getEndPositionOfChar()
    • getExtentOfChar()
    • getNumberOfChars()
    • getRotationOfChar()
    • getStartPositionOfChar()
    • getSubStringLength()
    • selectSubString()
  • SVGUse:

    • instanceRoot
  • window:

    • getComputedStyle()
    • scrollBy(),scrollTo(),scrollX,scrollY
    • webkitConvertPointFromNodeToPage(),webkitConvertPointFromPageToNode()

For a more comprehensive list of attributes, visit this Gist

indisplay:noneIs operated on

If you need to do a lot of work on a DOM element, you can take the DOM element “offline” — display: None from the DOM tree, and then “online” undisplay: None. This removes the backflow and redraw caused during the operation.

Operating cloneNode

You can also clone the current node and replace the original node after cloning the node.

Browser optimization

Rearrangements and redraws are easy and expensive to cause, and if every JavaScript action is rearranged and redrawn, the browser may not be able to handle it. So many browsers optimize these operations by maintaining a queue and putting all operations that cause reordering and redrawing into that queue. When the number of operations in the queue reaches a certain number of times or at a certain interval, the browser flush the queue and perform a batch. This will turn multiple rearrangements and redraws into one rearrangement and redraw.

var dom = document.querySelector("#dom"); Layout var newWidth = dom.offsetwidth + 10; // Read aDiv.style.width = newWidth + 'px'; // Write var newHeight = dom.offsetHeight + 10; // Read aDiv.style.height = newHeight + 'px'; // Write // Trigger layout only once var newWidth = dom.offsetwidth + 10; // Read var newHeight = dom.offsetHeight + 10; // Read aDiv.style.width = newWidth + 'px'; // Write aDiv.style.height = newHeight + 'px'; // WriteCopy the code

Modifying elements once

Every time a DOM element is modified, it may cause backflow and redraw of the browser. As far as possible, the number of changes is as small as possible. This is the same as the above idea of optimizing DOM reading and writing.

Change the style of the element by style
// bad
var dom = document.getElementById('dom');
dom.style.color = '#FFF';
dom.style.fontSize = '12px';
dom.style.width = '200px';
Copy the code

The above example triggers a redraw of the element every time the style property is changed, which will cause backflow if the size and position of the changed property are involved. Instead of setting the style attribute for an element more than once, change the style by adding new CSS classes to it.

<! --better--> <style> .my-style { color: #FFF; font-size: 12px; width: 200px; } </style> <script> var dom = document.getElementById('dom'); dom.classList.add('my-style'); </script>Copy the code
cssText

The same optimization idea as above can be achieved with cssText.

var dom = document.getElementById('dom'); dom.style.cssText = 'color: #FFF; font-size: 12px; width: 200px; 'Copy the code

Simplified DOM structure

First, each DOM object occupies browser resources, and the number of resources occupied is positively correlated. In addition, the deeper the DOM structure, the more ancestor DOM changes to the innermost DOM element can cause.

Using scenarios such as the presentation of large tables of data, tens of thousands of DOM’s can cause the browser to jam or crash. I have come across real cases where front-end pseudo paging was used while leaving the back-end interface unchanged.

DOM event optimization

Use event delegates or event brokers

Using an event broker saves more memory than binding events to each element. As an added bonus, the addition of fake DOM elements does not require binding events, which are not detailed here.

Function of the river

First, in this scenario, some operations need to be performed according to the scrolling position of the page during page scrolling. However, the Scroll event is triggered too frequently, resulting in high execution frequency and high overhead of bound events. We need to do something to reduce the frequency of events being executed.

Throttling actually reduces the frequency of function firing.

let throttle = (func, wait) => { let context, args; let previous = 0; return function () { var now = +new Date(); context = this; args = arguments; if (now - previous > wait) { func.apply(context, args); previous = now; }}; };Copy the code

Image stabilization function

When it comes to throttling, anti-vibration has to be improved. When it comes to throttling, the anti-vibration function actually delays the execution of the function. In general, anti-vibration saves more performance than current interception.

let debounce = (func, wait) => {
    let timeout;
    return function () {
        let context = this;
        let args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function () {
            func.apply(context, args)
        }, wait);
    };
};
Copy the code

Use scenarios such as real-time search of an input box. For users, the key words they want to input are the final result of the input, and the program needs to respond to the invalid key words entered by users in real time, which is undoubtedly a waste. We need to

CSS

Changes in the style of elements in the document flow can trigger browser backflow, and the larger the DOM tree affected, the longer it takes to redraw, which can lead to performance problems. CSS Triggers lists the attributes that trigger the browser, backflow, and redraw.

Use positioning to take elements out of the document flow

Using positioning to take elements out of the document stream, the DOM tree range that triggers backflow redrawing is greatly reduced.

.selector {
    position: fixed;
    // or
    position: absolute; 
}
Copy the code

Use transform and opacity

Transform and opacity ensure that changes to element attributes do not affect, nor are they affected by, the document flow, and do not cause redrawing.

FLTP

FLIP comes from First, Last, Invert, Play. FLIP maps expensive animations, such as those for width, height, left, or top, to Transform animations. By taking two snapshots of the element, one of its initial position (First — F) and the other of its final position (Last — L), and then inverting the element with a transform (Invert -i), the element looks like it’s still in its initial position. Finally, remove the transform on the element to move the element from its initial position (Play -p) to its final position.

Trigger GPU acceleration

Using GPU hardware acceleration can make browser animation smoother, but don’t be greedy, GPU acceleration is at the expense of hardware resources, which will lead to reduced battery life of mobile devices.

.selectror { webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); }.selector {webkit-transform: translate3d(0,0,0); Moz - transform: translate3d (0, 0); - ms - transform: translate3d (0, 0); - o - transform: translate3d (0, 0); The transform: translate3d (0, 0); }Copy the code

The transform might behave unexpectedly in the browser, such as flashing, and so on. You can hack it with code like this:

.selector {
    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    backface-visibility: hidden;

    -webkit-perspective: 1000;
    -moz-perspective: 1000;
    -ms-perspective: 1000;
    perspective: 1000;
}
Copy the code

will-change

The previous approach is to trick the browser into thinking it needs GPU acceleration, while will-change is a polite way of telling the browser “there will be a change here, please be prepared”. But don’t drink too much. Use it in moderation.

.selector {
    will-change: auto
    will-change: scroll-position
    will-change: contents
    will-change: transform        // Example of <custom-ident> 
    will-change: opacity          // Example of <custom-ident>
    will-change: left, top        // Example of two <animateable-feature>
    
    will-change: unset
    will-change: initial
    will-change: inherit
}
Copy the code

Avoid complex CSS selectors as wellcalc

Complex CSS selectors cause browsers to do a lot of computation and should be avoided

.box:nth-last-child(-n+1) .title {
  /* styles */
}   
Copy the code

Avoid using expensive CSS properties in animations

Some CSS properties have performance issues and can cause a lot of browser computations, especially in animation, so we should use them with caution.

  • box-shaow
  • background-image:
  • filter
  • border-radius
  • transforms
  • filters

Use flexbox layouts instead of floating layouts

New Flexbox are generally faster than older Flexbox or floating-based layout models

Canvas

For scenes with inconsistent rendering frequency, split-screen rendering is adopted

In some animated scenes, such as games, the background changes less than the movement of game objects, so we can separate these from objects with lower new frequency to form a Canvas layer with lower update frequency.

Frame rate vs. frame generation

Frame rate or frame rate is a measure used to measure the number of frames displayed. It is measured in “Frame per Second” (FPS) or “Hertz,” which is generally used to describe how many frames are played per Second for video, electronic graphics or games. via Wikipedia

The above said so much, in fact, are in the experience of the human eye service. The average movie frame rate is 24 frames per second, which is acceptable to the average person. However, games and screen motion efficiency pursue 60 frames or more, because movie images are pre-processed, and motion images contain motion information — that is, the fuzzy sensation generated by our eyes on fast-moving objects, and the human brain imagines motion based on this fuzzy sensation. However, many games or interactive animations are drawn in real time, which does not include blur and cannot be imagined by human brain naturally, so the framerate is more demanding, which is why some games have dynamic blur to compensate for the lack of frame rate to improve the game look and feel of the option.

Use microtasks to decompose large amounts of computation

In addition to the frame rate that people focus on, frame generation time is also important. If the frame rate is good but the generation time is not constant enough, it is easy to produce the feeling of frame skipping, just like rat dross in a pot of porridge. The solution is to break down computationally intensive operations and maintain a list of tasks to be evenly distributed over refresh intervals. Thank you nie Jun in explaining the game refresh rate inspiration, playing games can also learn knowledge! Oh, cross field this is the slogan of the machine core ~ ~

userequestAnimationFrame

RequestAnimationFrame takes full advantage of the refresh mechanism of the display and synchronizes with the refresh rate of the browser, resulting in smoother animations than setTimeOut or setInterval.

In addition, when the requestAnimationFrame page is inactive, the animation will stop executing, which will save even more energy on the machine.

Web Worker

JavaScript is single-threaded, and a lot of computation blocks the thread, causing the browser to drop frames. Web workers give JavaScript the ability to open up new threads, and we can process pure computation in Web workers.

reference

  1. Developers.google.com/web/fundame…
  2. Bubkoo.com/2016/03/31/…