Have you used these APIs commonly used in advanced front-end?

Dachang Technology Advanced Front-end Node Advanced

Click on the top programmer's growth guide, pay attention to the public number

Reply 1, join the advanced Node exchange group

This article is included in github github.com/Michael-lzg…

  • MutationObserver

  • IntersectionObserver

  • getComputedStyle()

  • getBoundingClientRect

  • requestAnimationFrame

MutationObserver

MutationObserver is an interface that can monitor changes in the DOM structure. The MutationObserver will be notified when there is any change in the DOM object tree.

API

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

  • mutations: list of node change records(sequence<MutationRecord>)

  • observer: Construct a MutationObserver object.

The MutationObserver object has three methods, as follows:

  • observe: set the observation target, accept two parameters, target: observation target, options: set observation options through object members

  • disconnect: prevent the observer from observing any changes

  • takeRecords: empty the record queue and return the contents

//选择一个需要观察的节点
var targetNode = document.getElementById('root')

// 设置observer的配置选项
var config = { attributes: true, childList: true, subtree: true }

// 当节点发生变化时的需要执行的函数
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.')
    }
  }
}

// 创建一个observer示例与回调函数相关联
var observer = new MutationObserver(callback)

//使用配置文件对目标节点进行观测
observer.observe(targetNode, config)

// 停止观测
observer.disconnect()

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

  • childList: Set to true, which means to observe the changes of the target child node, such as adding or deleting the target child node, excluding the modification of the child node and the changes of the descendants of the child node

  • Attributes: set to true, indicating changes in the attributes of the observation target

  • characterData: set to true, indicating the change of the observation target data

  • subtree: set to true, the target and the descendants of the target change 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 characterData setting can be omitted.

  • attributeFilter: If not all attribute changes need to be observed, and attributes is set to true or ignored, then sets a list of local names of attributes to observe (no namespace required)

Features

MutationObserver has the following characteristics:

  • It waits for all script tasks to complete before running, that is, asynchronously

  • It encapsulates DOM change records into an array for processing, rather than processing DOM changes one by one.

  • It can observe all changes that occur in DOM nodes, and can also observe a certain type of changes

The MutationObserver event is fired when the DOM changes. However, it is essentially different from an event: the event is triggered synchronously, that is to say, the corresponding event will be triggered immediately when the DOM changes; the MutationObserver is triggered asynchronously. After the DOM changes, it will not be triggered immediately, but will wait until the current Fired after all DOM manipulations have finished.

For example, if 1000 paragraphs (p elements) are continuously inserted into the 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 freeze; while MutationObserver is completely different, only It will be triggered after 1000 paragraphs have been inserted, and it will only be triggered once, which reduces the frequent changes of the DOM and greatly benefits the performance.

IntersectionObserver

When developing web pages, it is often necessary to know whether an element has entered the "viewport", that is, whether the user can see it.

The traditional implementation method is to call the getBoundingClientRect() method of the target element after listening to the scroll event to obtain the coordinates corresponding to the upper left corner of the viewport, and then determine whether it is within the viewport. The disadvantage of this method is that due to the intensive occurrence of scroll events, the amount of calculation is very large, which is easy to cause performance problems.

There is currently a new IntersectionObserver API that automatically "observes" whether an element is visible, which Chrome 51+ already supports. Since the essence of visible is that the target element produces an intersection with the viewport, this API is called "intersection viewer".

API

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

var io = new IntersectionObserver(callback, option)

// 开始观察
io.observe(document.getElementById('example'))

// 停止观察
io.unobserve(element)

// 关闭观察器
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 observer's callback function callback is called. The callback is usually fired twice. Once when the target element has just entered the viewport (starting visible), and once when it leaves the viewport completely (starting invisible).

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

The parameter (entries) of the callback function is an array, 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 the visibility changed, it is a high-precision timestamp in milliseconds

  • target: the target element to be observed, is a DOM node object

  • isIntersecting: whether the target is visible

  • rootBounds: Information about the rectangular area of ​​the root element, the return value of the getBoundingClientRect() method, or null if there is no root element (that is, scrolling directly relative to the viewport)

  • boundingClientRect: Information about the rectangular area of ​​the target element

  • intersectionRect: Information about the intersection area of ​​the target element and the viewport (or root element)

  • intersectionRatio: The visible ratio of the target element, that is, the ratio of intersectionRect to boundingClientRect, 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">首页</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 = '我出来了'
            } else {
              div1.innerText = '首页'
            }
          })
        },
        {
          root: null,
          threshold: [0, 1]
        }
      )

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

Compared with getBoundingClientRect, it has the advantage of not causing redrawing and reflow. Compatibility is as follows

716b7091ed3aa9159f470657c26598ef.png
IntersectionObserver.png

Image lazy loading

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

const imgs = document.querySelectorAll('img[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('src')
      }
      // 解除观察
      self.unobserve(entry.target)
    }
  })
}, config)

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

infinite scroll

The implementation of infinite scroll is also simple.

var intersectionObserver = new IntersectionObserver(function (entries) {
  // 如果不可见,就返回
  if (entries[0].intersectionRatio <= 0) return
  loadItems(10)
  console.log('Loaded new items')
})

// 开始观察
intersectionObserver.observe(document.querySelector('.scrollerFooter'))

getComputedStyle()

DOM2 document.defaultViewStyle adds the getComputedStyle() method on , which returns an CSSStyleDeclarationobject (of the same type as the style attribute) containing the computed style for 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 computed style from and a pseudo-element string (eg ":after"). If you don't need to query pseudo elements, you can pass null as the second parameter.

<!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 with style

The similarity 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 element's style attribute; and 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 the element through element.style. And getComputedStyle only supports reading and does not support writing. We can read the style 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, which is the element's CSS border size. The returned result is the smallest rectangle containing the complete element, and has left, top, right, bottom, x, y, width, and height read-only properties in pixels that describe the entire border. Properties other than width and height are calculated relative to the upper left corner of the view window.

2a56f859dc8d9dde4353888f232a332e.png
getBoundingClientRect.png

Application scenarios

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

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

// 获取dom元素相对于网页左上角定位的距离
function offset(el) {
  var top = 0
  var left = 0
  do {
    top += el.offsetTop
    left += el.offsetLeft
  } while ((el = el.offsetParent)) // 存在兼容性问题,需要兼容
  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 like this:

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 // 元素顶端到可见区域顶端的距离
  var bottom = el.getBoundingClientRect().bottom // 元素底部端到可见区域顶端的距离
  var se = document.documentElement.clientHeight // 浏览器可见区域高度。
  if (top < se && bottom > 0) {
    return true
  } else if (top >= se || bottom <= 0) {
    // 不可见
  }
  return false
}

requestAnimationFrame

window.requestAnimationFrame() tells the browser that you want to perform an animation and asks the browser to call the specified callback function to update the animation before the next repaint.

API

This method needs to pass in a callback function as a parameter, the callback function will be executed before the browser's next repaint.

window.requestAnimationFrame(callback)

Compatibility Handling

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

end animation

var globalID
function animate() {
  // done(); 一直运行
  globalID = requestAnimationFrame(animate) // Do something animate
}
globalID = requestAnimationFrame(animate) //开始
cancelAnimationFrame(globalID) //结束

Compared with setTimeout, the biggest advantage of requestAnimationFrame is that the system determines the execution timing of the callback function. Specifically, if the screen refresh rate is 60Hz, then the callback function is executed every 16.7ms. If the refresh rate is 75Hz, then the time interval becomes 1000/75=13.3ms, in other words, the requestAnimationFrame The pace 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 that it will not cause frame loss or cause the animation to freeze. The call to this API is simple as follows:

var progress = 0
//回调函数
function render() {
  progress += 1 //修改图像的位置
  if (progress < 100) {
    //在动画没有结束前,递归渲染
    window.requestAnimationFrame(render)
  }
}
//第一帧渲染
window.requestAnimationFrame(render)

advantage:

  • CPU energy saving: For animations implemented with setTimeout, when the page is hidden or minimized, setTimeout still performs animation tasks in the background. Since the page is invisible or unavailable at this time, it is meaningless to refresh the animation, which is 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, so the requestAnimationFrame that follows the system will also stop rendering. When the page is activated, the animation will start from the last time. The execution continues where it left off, 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 in each refresh interval, which can ensure smoothness It can also better save the overhead of function execution. It doesn't make sense for the function to execute multiple times within a refresh interval, because the display refreshes every 16.7ms, and multiple draws are not reflected on the screen.

Application scenarios

1. Monitor the scroll function

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

$(window).on('scroll', function () {
  window.requestAnimationFrame(scrollHandler)
})

Smooth scroll 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. Large amount of data rendering

For example, the rendering of 100,000 pieces of data mainly includes the following methods:

(1) Using a timer

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  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) Use requestAnimationFrame

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  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

Calculate the FPS of a web page every second, get a column of data, and then analyze it. 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. If 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, such as three consecutive FPS lower than 20, it can be considered that the web page is stuck.

——End of this article——

Author: lzg9527 

Original text: https://juejin.cn/post/7028744289890861063

Node 社群


我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:

1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长

点赞和在看就是最大的支持❤️

Guess you like

Origin blog.csdn.net/xgangzai/article/details/123540242