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

Keywords: Javascript Front-end html5

This article is included in github https://github.com/Michael-lz...

MutationObserver

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

API

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
observer.disconnect()

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

characteristic

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.

IntersectionObserver

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".

API

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
io.observe(document.getElementById('example'))

// Stop observing
io.unobserve(element)

// Close the viewer
io.disconnect()

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

io.observe(elementA)
io.observe(elementB)

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) => {
  console.log(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">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #div1 {
        position: sticky;
        top: 0;
        height: 50px;
        line-height: 50px;
        text-align: center;
        background: black;
        color: #ffffff;
        font-size: 18px;
      }
    </style>
  </head>

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

      observer.observe(div2)
    </script>
  </body>
</html>

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 = entry.target
      let src = img.dataset.src
      if (src) {
        img.src = src
        img.removeAttribute('data-src')
      }
      // Release observation
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) => {
  observer.observe(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
  loadItems(10)
  console.log('Loaded new items')
})

// Start observing
intersectionObserver.observe(document.querySelector('.scrollerFooter'))

getComputedStyle()

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.

API

document.defaultView.getComputedStyle(element[,pseudo-element])
// or
window.getComputedStyle(element[,pseudo-element])

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>
<html>
  <head>
    <style type="text/css">
      #myDiv {
        background-color: blue;
        width: 100px;
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="myDiv" style="background-color: red; border: 1px solid black"></div>
  </body>
  <script>
    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'))
  </script>
</html>

Similarities and differences between and style

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

  • element.style 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.
  • Element.style supports both reading and writing. We can rewrite the style of elements through element.style. getComputedStyle only supports reading and does not support writing. We can read the style by using getComputedStyle and modify the style through element.style

getBoundingClientRect

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

API

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
}

requestAnimationFrame

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.

API

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

window.requestAnimationFrame(callback)

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
    window.requestAnimationFrame(render)
  }
}
//First frame rendering
window.requestAnimationFrame(render)

advantage:

  • 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 () {
  window.requestAnimationFrame(scrollHandler)
})

Scroll smoothly to the top of the page

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

scrollToTop()

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
//PageCount 
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)
      ul.appendChild(li)
    }
    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
//PageCount 
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)
      ul.appendChild(li)
    }
    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 = performance.now()
var frame = 0
var lastFameTime = performance.now()
var loop = function (time) {
  var now = performance.now()
  var fs = now - lastFameTime
  lastFameTime = now
  var fps = Math.round(1000 / fs)
  frame++
  if (now > 1000 + lastTime) {
    var fps = Math.round((frame * 1000) / (now - lastTime))
    frame = 0
    lastTime = now
  }
  window.requestAnimationFrame(loop)
}

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