This article is featured on Github github.com/Michael-lzg…

MutationObserver

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

API

MutationObserver is a constructor that takes a callback to handle node changes and returns two arguments:

  • Mutations: records list of node changes(sequence < MutationRecord >)
  • Observer: Constructs a MutationObserver object.

The MutationObserver object has three methods, as follows:

  • Observe: Set the observing object. Accept two parameters: target: observe object, and options: Set observing options through an object member
  • Disconnect: Blocks the observer from seeing any changes
  • TakeRecords: Clears the record queue and returns its contents
// Select a node to observe
var targetNode = document.getElementById('root')

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

// The function that needs 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 observing
observer.disconnect()
Copy the code

The observe method options parameter has the following options:

  • ChildList: Set true to observe changes to the target child node, such as adding or deleting the target child node, excluding changes to the child node and descendants of the child node
  • Attributes: Sets true to observe changes in target attributes
  • CharacterData: Set to true to observe the change of target data
  • Subtree: Set to true, changes to the target and its descendants are observed
  • AttributeOldValue: If the attribute is true or omitted, it is set to true, indicating that the value of the target attribute before the change needs to be recorded. If you set attributeOldValue, you can omit the attributes setting
  • CharacterDataOldValue: If the characterData setting is true or omitted, the characterData setting is set to True, indicating that the target data before the change needs to be recorded. If the characterData setting is set to True, the characterData setting can be omitted
  • AttributeFilter: If not all attribute changes need to be observed and attributes are set to true or ignored, set a list of local names (namespace-free) of attributes that need to be observed

The characteristics of

MutationObserver has the following features:

  • It waits for all script tasks to complete before running, that is, asynchronously
  • Instead of processing DOM changes individually, it wraps DOM changes into an array for processing.
  • It can observe all changes that occur in a DOM node, as well as a class of changes

A MutationObserver event is raised when the DOM changes. However, it differs from events in one essential way: events are synchronously triggered, meaning that DOM changes immediately trigger corresponding events; MutationObserver is triggered asynchronously, not immediately after a DOM change, but after all current DOM operations have completed.

For example, if you insert 1000 consecutive paragraphs (p elements) into a document, 1000 consecutive insert events will be triggered, executing the callback function for each event, which is likely to cause the browser to lag. MutationObserver, on the other hand, fires only once after all 1000 paragraphs have been inserted, which reduces DOM variability and greatly improves performance.

IntersectionObserver

When developing a web page, it is often necessary to know whether an element has entered a “viewport,” or whether the user can see it.

The traditional implementation method is to listen to the Scroll event, call the getBoundingClientRect() method of the target element, get its 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 it is easy to cause performance problems due to the intensive occurrence of Scroll events and large amount of computation.

There is a new IntersectionObserver API that can automatically “observe” whether elements are visible, and Chrome 51+ already supports it. Because the nature of visible is that a target element intersects with the viewport, the API is called a “intersecting viewer.”

API

IntersectionObserver is a constructor provided natively by the browser and 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()
Copy the code

Call this method multiple times if you want to observe multiple nodes.

io.observe(elementA)
io.observe(elementB)
Copy the code

When the visibility of the target element changes, the observer’s callback function is called. Callback typically fires twice. Once the target element has just entered the viewport (becoming visible), and once it has completely left the viewport (becoming invisible).

var io = new IntersectionObserver((entries) = > {
  console.log(entries)
})
Copy the code

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

  • Time: indicates 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: Whether the target is visible
  • RootBounds: Information about the rectangular region of the root element, returned by the getBoundingClientRect() method, or null if there is no root element (that is, scrolling directly relative to the viewport)
  • BoundingClientRect: Information about the rectangular region of the target element
  • IntersectionRect: information on the intersection area between target element and viewport (or root element)
  • IntersectionRatio: Visibility ratio of target elements, i.e., proportion of intersectionRect to boundingClientRect, is 1 when it is completely visible, and less than or equal to 0 when it is completely invisible

For example

<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 coming out.'
            } else {
              div1.innerText = 'home'}})}, {root: null.threshold: [0.1]
        }
      )

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

The advantage over getBoundingClientRect is that it does not cause redraw backflow. Compatibility is as follows

Lazy loading of images

The principle of picture lazy loading is mainly to judge whether the current picture has reached the visual area of the core logic. This saves bandwidth and improves web page performance. Traditional lazy loading is achieved by listening for Scroll events, but scroll events will trigger many times in a very short period of time, seriously affecting page performance. To improve page performance, IntersectionObserver can be used to implement lazy image loading.

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')}// Remove the observation
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) = > {
  observer.observe(image)
})
Copy the code

The infinite scroll

The implementation of infinite Scroll is also very simple.

var intersectionObserver = new IntersectionObserver(function (entries) {
  // If not visible, return
  if (entries[0].intersectionRatio <= 0) return
  loadItems(10)
  console.log('Loaded new items')})// Start observing
intersectionObserver.observe(document.querySelector('.scrollerFooter'))
Copy the code

getComputedStyle()

DOM2 Style adds the getComputedStyle() method on Document.defaultView, which returns a CSSStyleDeclaration object (of the same type as the Style property) containing the element’s computed Style.

API

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

This method takes two arguments: the element to get the computed style and a pseudo-element string (such as “:after”). The second argument can be passed null if no pseudo-element query is required.

<! DOCTYPEhtml>
<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>
Copy the code

And style similarities and differences

The similarity between getComputedStyle and Element. style is that both return a CSSStyleDeclaration object. The difference is:

  • Style reads only the inline style of the element, that is, the style written on the element’s style property; GetComputedStyle reads the final style, including inline, embedded, and external styles.
  • Element. style supports both reading and writing, and we can rewrite elements with element.style. GetComputedStyle only supports reads, not writes. We can read styles using getComputedStyle and modify styles using element.style

getBoundingClientRect

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

API

let DOMRect = object.getBoundingClientRect()
Copy the code

The return value is a DOMRect object, which is the set of rectangles returned by the element’s getClientRects() method, and is the CSS border size of the element. The result is the smallest rectangle that contains the entire element and has the left, top, right, bottom, x, y, width, and height read-only attributes in pixels that describe the entire border. Properties other than width and height are evaluated relative to the top left corner of the view window.

Application scenarios

1. Get the distance of the DOM element relative to the upper left corner of the page

It used to be found by offsetParent to locate the parent element and recurse to the top-level element body or HTML.

// Gets the distance from the dom element to the upper left corner of the page
function offset(el) {
  var top = 0
  var left = 0
  do {
    top += el.offsetTop
    left += el.offsetLeft
  } while ((el = el.offsetParent)) // Compatibility problems exist and need to be compatible
  return {
    top: top,
    left: left
  }
}

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

Now, using the getBoundingClientRect API, we can write it like this:

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

2. Determine if the element is in the viewable 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 browser visible area.
  if (top < se && bottom > 0) {
    return true
  } else if (top >= se || bottom <= 0) {
    / / not visible
  }
  return false
}
Copy the code

requestAnimationFrame

Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation.

API

This method takes as an argument a callback function that is executed before the browser’s next redraw.

window.requestAnimationFrame(callback)
Copy the code

Compatibility processing

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

The end of the animation,

var globalID
function animate() {
  // done(); Has been running
  globalID = requestAnimationFrame(animate) // Do something animate
}
globalID = requestAnimationFrame(animate) / /
cancelAnimationFrame(globalID) / / end
Copy the code

The biggest advantage of requestAnimationFrame over setTimeout is that the system decides when to execute the callback function. To be more specific, if the screen refresh rate is 60Hz, then the callback function is executed every 16.7ms. If the screen refresh rate is 75 hz, then the interval becomes 1000/75=13.3ms. In other words, RequestAnimationFrame moves at the pace of the system refresh. It ensures that the callback function is executed only once in each screen refresh interval, thus avoiding frame loss and animation stuttering. The API call is simple, as follows:

var progress = 0
// The callback function
function render() {
  progress += 1 // Change the position of the image
  if (progress < 100) {
    // Render recursively before the animation ends
    window.requestAnimationFrame(render)
  }
}
// Render first frame
window.requestAnimationFrame(render)
Copy the code

Advantages:

  • CPU energy saving: When using setTimeout to achieve animation, when the page is hidden or minimized, setTimeout is still performing animation tasks in the background, because the page is not visible or unavailable at this time, refreshing animation is meaningless, it is a complete waste of CPU resources. RequestAnimationFrame is completely different. When the page is not active, the screen refresh task of the page is also suspended, so the requestAnimationFrame that follows the pace of the system will also stop rendering. When the page is active, The animation picks up where it left off, effectively saving CPU overhead.

  • Function throttling: In high frequency events (resize, Scroll, etc.), to prevent multiple function executions within a refresh interval, requestAnimationFrame can be used to ensure that functions are executed only once within each refresh interval, which can ensure smooth performance and reduce the cost of function execution. It does not make sense if the function is executed more than once in a refresh interval, because the monitor is refreshed every 16.7ms and multiple draws do not show up on the screen.

Application scenarios

1, Listen for scroll function

A listener for the scroll event is a good fit for this API, deferred until the next rendering.

$(window).on('scroll'.function () {
  window.requestAnimationFrame(scrollHandler)
})
Copy the code

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()
Copy the code

2. Large amount of data rendering

For example, to render 100,000 pieces of data, there are mainly the following methods:

(1) Use timers

// The container to insert
let ul = document.getElementById('container')
// Insert 100,000 pieces of data
let total = 100000
// Insert 20 strips at a time
let once = 20
/ / the total number of pages
let page = total / once
// The index of each record
let index = 0
// loop to load data
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  // How many pages 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)
Copy the code

2) Use requestAnimationFrame

// The container to insert
let ul = document.getElementById('container')
// Insert 100,000 pieces of data
let total = 100000
// Insert 20 strips at a time
let once = 20
/ / the total number of pages
let page = total / once
// The index of each record
let index = 0
// loop to load data
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  // How many pages 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)
Copy the code

Monitor the Caton method

Compute the FPS of a web page once per second, get a column of data, and analyze it. The requestAnimationFrame API is used to execute javascript code on a regular basis. If the browser is running late, the render rate is not up to 60 frames in 1s, which can indirectly reflect the browser’s render rate.

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)
}
Copy the code

We can define some boundary values, such as 3 FPS below 20 in a row to consider a web page to be stuck.

Recommend the article

You must know the webPack plugin principle analysis
Asynchronous loading principle and subcontracting strategy of Webpack
Summary of 18 Webpack plugins, there’s always something you want!
Build a VuE-CLI4 + WebPack mobile framework (out of the box)
Build from scratch to optimize a vue-CLI-like scaffolding
Encapsulate a TOAST and Dialog component and publish it to NPM
Build a WebPack project from scratch
Summary of several webpack optimization methods
Summary of advanced application of VUE knowledge system
Summarize the practical skills of vUE knowledge system
Summarize the basic introduction to vUE knowledge system
Summary of mobile H5 development tips.