Do you use these API s commonly used in advanced front ends?

Keywords: Javascript Front-end html5

This article is included in github


MutationObserver is an interface that can listen for DOM structure changes. The MutationObserver is notified of any changes to the DOM object tree.


MutationObserver is a constructor that accepts a callback parameter and is used to handle node changes. It returns two parameters:

  • Changes: list of node change records (sequence < mutationrecord >)
  • observer: construct the MutationObserver object.

The MutationObserver object has three methods, as follows:

  • observe: set the observation target and accept two parameters. Target: observation target and options: set the observation options through object members
  • disconnect: prevents the observer from observing any changes
  • Take records: clear the record queue and return the contents
//Select a node to observe
var targetNode = document.getElementById('root')

// Set the configuration options for observer
var config = { attributes: true, childList: true, subtree: true }

// The function to be executed when the node changes
var callback = function (mutationsList, observer) {
  for (var mutation of mutationsList) {
    if (mutation.type == 'childList') {
      console.log('A child node has been added or removed.')
    } else if (mutation.type == 'attributes') {
      console.log('The ' + mutation.attributeName + ' attribute was modified.')

// Create an observer example associated with the callback function
var observer = new MutationObserver(callback)

//Use the configuration file to observe the target node
observer.observe(targetNode, config)

// Stop observation

The options parameter in the observe method has the following options:

  • childList: set true to observe the changes of target child nodes, such as adding or deleting target child nodes, excluding the changes of modifying child nodes and descendants of child nodes
  • Attributes: set true to observe the change of target attributes
  • characterData: set true to observe the change of target data
  • subtree: if set to true, the target and its descendants will be observed
  • attributeOldValue: if the attribute is true or omitted, it is equivalent to setting it to true, indicating that the target attribute value before the change needs to be recorded. If attributeOldValue is set, the attributes setting can be omitted
  • characterDataOldValue: if characterData is true or omitted, it is equivalent to setting it to true, indicating that the target data before the change needs to be recorded. If characterDataOldValue is set, the setting of characterData can be omitted
  • attributeFilter: if not all attribute changes need to be observed, and attributes is set to true or ignored, set a list of local names (no namespace) of attributes to be observed


MutationObserver has the following features:

  • It will not run until all script tasks are completed, that is, it will run asynchronously
  • It encapsulates DOM change records into an array for processing, rather than processing DOM changes individually one by one.
  • It can observe all changes that occur in the DOM node, and it can also observe a certain kind of changes

When the DOM changes, the MutationObserver event will be triggered. However, it is essentially different from events: events are triggered synchronously, that is, changes in DOM will trigger corresponding events immediately; MutationObserver is triggered asynchronously. After DOM changes, it will not be triggered immediately, but will not be triggered until all current DOM operations are completed.

For example, if 1000 paragraphs (p elements) are continuously inserted into a document, 1000 insertion events will be triggered continuously, and the callback function of each event will be executed, which is likely to cause the browser to jam; The MutationObserver is completely different. It is triggered only after 1000 paragraphs are inserted, and it is triggered only once, which reduces the frequent changes of DOM and is greatly conducive to performance.


When developing web pages, we often need to know whether an element enters the "viewport", that is, whether users can see it.

The traditional implementation method is to monitor the scroll event and then call the getBoundingClientRect() method of the target element to get the coordinates corresponding to the upper left corner of the viewport, and then judge whether it is in the viewport. The disadvantage of this method is that due to the intensive occurrence of scroll events and large amount of calculation, it is easy to cause performance problems.

At present, there is a new IntersectionObserver API that can automatically "observe" whether elements are visible. Chrome 51 + already supports it. Because the essence of visible is that the target element and the viewport produce a cross area, this API is called "cross viewer".


IntersectionObserver is a browser native constructor that accepts two parameters: callback is the callback function when visibility changes, and option is the configuration object (this parameter is optional).

var io = new IntersectionObserver(callback, option)

// Start observing

// Stop observing

// Close the viewer

If you want to observe multiple nodes, you need to call this method multiple times.


When the visibility of the target element changes, the callback function of the observer is called. Callback is usually triggered twice. One is when the target element just enters the viewport (becomes visible), and the other is when it leaves the viewport completely (becomes invisible).

var io = new IntersectionObserver((entries) => {

The parameters (entries) of the callback function are an array, and each member is an IntersectionObserverEntry object. For example, if the visibility of two observed objects changes at the same time, the entries array will have two members.

  • Time: the time when visibility changes. It is a high-precision timestamp, in milliseconds
  • Target: the observed target element, which is a DOM node object
  • isIntersecting: is the target visible
  • Rootboundaries: information about the rectangular area of the root element. The return value of the getBoundingClientRect() method. If there is no root element (i.e. scrolling directly relative to the viewport), null is returned
  • boundingClientRect: information about the rectangular area of the target element
  • intersectionRect: information about the intersection area between the target element and the viewport (or root element)
  • intersectionRatio: the visible proportion of the target element, that is, the proportion of intersectionRect to boundingClientRect. It is 1 when it is completely visible and less than or equal to 0 when it is completely invisible

for instance

<html lang="en">
    <meta charset="UTF-8" />
      #div1 {
        position: sticky;
        top: 0;
        height: 50px;
        line-height: 50px;
        text-align: center;
        background: black;
        color: #ffffff;
        font-size: 18px;

    <div id="div1">home page</div>
    <div style="height: 1000px;"></div>
    <div id="div2" style="height: 100px; background: red;"></div>
      var div2 = document.getElementById('div2')
      let observer = new IntersectionObserver(
        function (entries) {
          entries.forEach(function (element, index) {
            if (element.isIntersecting) {
              div1.innerText = 'I'm out'
            } else {
              div1.innerText = 'home page'
          root: null,
          threshold: [0, 1]


Compared with getBoundingClientRect, its advantage is that it does not cause redrawing backflow. Compatibility is as follows

Lazy loading of pictures

The principle of image lazy loading is mainly realized by the core logic of judging whether the current image has reached the visual area. This can save bandwidth and improve web page performance. The traditional lazy loading is realized by listening to the scroll event, but the scroll event will be triggered many times in a very short time, which will seriously affect the page performance. To improve page performance, we can use IntersectionObserver to load images lazily.

const imgs = document.querySelectorAll('img[data-src]')
const config = {
  rootMargin: '0px',
  threshold: 0
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let img =
      let src = img.dataset.src
      if (src) {
        img.src = src
      // Release observation
}, config)

imgs.forEach((image) => {

Infinite scroll

The implementation of infinite scroll is also very simple.

var intersectionObserver = new IntersectionObserver(function (entries) {
  // If not, return
  if (entries[0].intersectionRatio <= 0) return
  console.log('Loaded new items')

// Start observing


DOM2 Style adds a getComputedStyle() method on document.defaultView, which returns a CSSStyleDeclaration
Object (the same type as the style attribute) that contains the calculation style of the element.


// or

This method takes two parameters: the element to get the calculation style and the pseudo element string (such as ": after"). If you do not need to query pseudo elements, the second parameter can be passed null.

<!DOCTYPE html>
    <style type="text/css">
      #myDiv {
        background-color: blue;
        width: 100px;
        height: 200px;
    <div id="myDiv" style="background-color: red; border: 1px solid black"></div>
    function getStyleByAttr(obj, name) {
      return window.getComputedStyle ? window.getComputedStyle(obj, null)[name] : obj.currentStyle[name]
    let node = document.getElementById('myDiv')
    console.log(getStyleByAttr(node, 'backgroundColor'))
    console.log(getStyleByAttr(node, 'width'))
    console.log(getStyleByAttr(node, 'height'))
    console.log(getStyleByAttr(node, 'border'))

Similarities and differences between and style

The same thing between getComputedStyle and is that both return CSSStyleDeclaration objects. The difference is:

  • reads only the inline style of the element, that is, the style written on the style attribute of the element; The style read by getComputedStyle is the final style, including inline style, embedded style and external style.
  • supports both reading and writing. We can rewrite the style of elements through getComputedStyle only supports reading and does not support writing. We can read the style by using getComputedStyle and modify the style through


The getBoundingClientRect() method returns the size of the element and its position relative to the viewport.


let DOMRect = object.getBoundingClientRect()

Its return value is a DOMRect object, which is a collection of rectangles returned by the element's getClientRects() method, that is, the CSS border size of the element. The returned result is the smallest rectangle containing the complete element, and has several read-only attributes in pixels: left, top, right, bottom, x, y, width, and height, which are used to describe the whole border. Properties other than width and height are calculated relative to the upper left corner of the view window.

Application scenario

1. Gets the distance the dom element is positioned relative to the upper left corner of the web page

The previous writing method is to find the element through offsetParent and locate the parent element until it recurses to the top-level element body or html.

// Gets the distance the dom element is positioned relative to the upper left corner of the web page
function offset(el) {
  var top = 0
  var left = 0
  do {
    top += el.offsetTop
    left += el.offsetLeft
  } while ((el = el.offsetParent)) // There is a compatibility problem and compatibility is required
  return {
    top: top,
    left: left

var odiv = document.getElementsByClassName('markdown-body')
offset(a[0]) // {top: 271, left: 136}

Now, according to the api getBoundingClientRect, it can be written as follows:

var positionX = this.getBoundingClientRect().left + document.documentElement.scrollLeft
var positionY = this.getBoundingClientRect().top + document.documentElement.scrollTop

2. Determine whether the element is in the visible area

function isElView(el) {
  var top = el.getBoundingClientRect().top // The distance from the top of the element to the top of the visible area
  var bottom = el.getBoundingClientRect().bottom // The distance from the bottom of the element to the top of the visible area
  var se = document.documentElement.clientHeight // The height of the visible area of the browser.
  if (top < se && bottom > 0) {
    return true
  } else if (top >= se || bottom <= 0) {
    // invisible
  return false


window.requestAnimationFrame() tells the browser that you want to execute an animation and ask the browser to update the animation before calling the specified callback function before next redraw.


This method needs to pass in a callback function as a parameter, which will be executed before the next redrawing of the browser.


Compatibility processing

window._requestAnimationFrame = (function () {
  return (
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60)

End animation

var globalID
function animate() {
  // done();  Always running
  globalID = requestAnimationFrame(animate) // Do something animate
globalID = requestAnimationFrame(animate) //start
cancelAnimationFrame(globalID) //end

Compared with setTimeout, the biggest advantage of requestAnimationFrame is that the system determines the execution time of the callback function. Specifically, if the screen refresh rate is 60Hz, the callback function is executed every 16.7ms. If the refresh rate is 75Hz, the time interval becomes 1000/75=13.3ms. In other words, the pace of requestAnimationFrame follows the pace of system refresh. It can ensure that the callback function is executed only once in each refresh interval of the screen, so it will not cause frame loss and jam in the animation. The API call is simple, as shown below:

var progress = 0
//Callback function
function render() {
  progress += 1 //Modify the position of the image
  if (progress < 100) {
    //Render recursively before the animation ends
//First frame rendering


  • CPU energy saving: for the animation implemented by setTimeout, when the page is hidden or minimized, setTimeout still performs the animation task in the background. Because the page is invisible or unavailable at this time, refreshing the animation is meaningless and a complete waste of CPU resources. The requestAnimationFrame is completely different. When the page processing is not activated, the screen refresh task of the page will also be suspended by the system. Therefore, the requestAnimationFrame following the pace of the system will also stop rendering. When the page is activated, the animation will continue to execute from the last place, effectively saving CPU overhead.
  • Function throttling: in high-frequency events (resize,scroll, etc.), in order to prevent multiple function executions within a refresh interval, use requestAnimationFrame to ensure that the function is executed only once within each refresh interval, which can not only ensure fluency, but also better save the cost of function execution. It is meaningless to execute the function multiple times within a refresh interval, because the display refreshes every 16.7ms, and multiple drawings will not be reflected on the screen.

Application scenario

1. Monitor scroll function

The listener function of page scroll event is very suitable for using this api and delaying it until the next re rendering.

$(window).on('scroll', function () {

Scroll smoothly to the top of the page

const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop
  if (c > 0) {
    window.scrollTo(0, c - c / 8)


2. Massive data rendering

For example, rendering 100000 pieces of data mainly consists of the following methods:

(1) Use timer

//Container to insert
let ul = document.getElementById('container')
// Insert 100000 pieces of data
let total = 100000
// Insert 20 at a time
let once = 20
let page = total / once
//Index of each record
let index = 0
//Cyclic loading data
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  //How many entries per page
  let pageCount = Math.min(curTotal, once)
  setTimeout(() => {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
    loop(curTotal - pageCount, curIndex + pageCount)
  }, 0)
loop(total, index)

(2) Using requestAnimationFrame

//Container to insert
let ul = document.getElementById('container')
// Insert 100000 pieces of data
let total = 100000
// Insert 20 at a time
let once = 20
let page = total / once
//Index of each record
let index = 0
//Cyclic loading data
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  //How many entries per page
  let pageCount = Math.min(curTotal, once)
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
    loop(curTotal - pageCount, curIndex + pageCount)
loop(total, index)

Monitoring Caton method

The FPS of the web page is calculated once per second, a column of data is obtained, and then analyzed. The popular explanation is that some JS code is executed regularly through the requestAnimationFrame API. If the browser is stuck, the rendering frequency cannot be well guaranteed, and the frame cannot reach 60 frames in 1s, it can indirectly reflect the rendering frame rate of the browser.

var lastTime =
var frame = 0
var lastFameTime =
var loop = function (time) {
  var now =
  var fs = now - lastFameTime
  lastFameTime = now
  var fps = Math.round(1000 / fs)
  if (now > 1000 + lastTime) {
    var fps = Math.round((frame * 1000) / (now - lastTime))
    frame = 0
    lastTime = now

We can define some boundary values. For example, if there are three FPS S lower than 20 in a row, it can be considered that there is a jam on the web page.

Recommended articles

You must know the principle analysis of webpack plug-in
Asynchronous loading principle and subcontracting strategy of Web pack
Summarize 18 webpack plug-ins, there will always be what you want!
Build a vue-cli4+webpack mobile terminal framework (out of the box)
From zero construction to optimization of a Vue cli like scaffold
Encapsulate a toast and dialog component and publish it to npm
Build a webpack project from scratch
Summarize several methods of Web pack packaging optimization
Advanced application of summarizing vue knowledge system
Summarize the practical skills of vue knowledge system
Introduction to summarizing vue knowledge system
Summarize the common skills of H5 development of mobile terminal (full of dry goods!)

Posted by gj6kings on Wed, 10 Nov 2021 17:39:09 -0800