Observe the API Intersection Observer

before

In the past, it was not easy to detect whether an element was visible or whether two elements intersected, and many solutions were unreliable or poorly performed. However, with the development of the Internet, the need is increasing. For example, intersection detection is required in the following situations:

  • Image lazy loading – images are loaded only when they are scrolled into view
  • Content infinite scrolling – that is, when the user scrolls near the bottom of the content, more is loaded directly, without the user having to turn the page, giving the user the illusion that the web page can scroll indefinitely
  • Detect AD exposure – In order to calculate AD revenue, you need to know the exposure of AD elements (buried points)
  • Perform a task or play an animation when the user sees an area

In the past, intersecting detection usually use event listeners, and need to frequently called Element. GetBoundingClientRect () method for the boundary of the related elements information. Event listeners, and invoking Element. GetBoundingClientRect () is run on the main thread, so frequent trigger, call may cause performance problems. The test is downright weird and inelegant

Intersection Observer WHAT is the Intersection Observer API

The element we need to observe is called the target element, the boundary box of the device window or other specified element viewport we call it the root element, or simply the root element.

The Intersection Observer API translates as a “crossover Observer” because the essence of determining whether an element is visible (as is often the case) is to determine whether the target element and the root element create an Intersection.

Why is this in normal case? When CSS is set opacity: 0, visibility: hidden, or other elements are used to cover the target element, it is not visible to the view, but is visible to the crossover viewer. This might be a little abstract, but remember that the crossover only cares about whether the target element and the root element have crossover regions, not whether the element is visually visible. Of course, if you set display: None, the crossover will not work, which makes sense because the element is no longer there, so it will not be monitored.

The Intersection Observer API provides a way to asynchronously detect changes in the Intersection of a target element with an ancestor element or viewport. — MDN

How to use the Intersection Observer API

Const myObserver = new IntersectionObserver(callback, options); // IntersectionObserver is IntersectionObserver const myObserver = new IntersectionObserver(callback, options);Copy the code

The browser’s native constructor IntersectionObserver is first called, and its return value is an observer instance.

The constructor IntersectionObserver accepts two arguments, callback and options

For ease of understanding, let’s look at the second parameter options. An object that can be used to configure an observer instance. What properties does this configuration object contain?

Options: Configure objects (optional, default Settings will be used when no transmission is performed)

The options argument received by the constructor

  • Root: Sets the root element of the target element, the area we use to determine whether the element is visible, to be the parent element of the target element. If not specified, the browser window is used, i.e. Document.

  • RootMargin: a set of offsets, of type string, that are added to the root boundary during calculation of cross values to effectively narrow or expand the rootMargin for calculation. The syntax is roughly the same as that of the MARGIN attribute in the CSS. The default value is 0px 0px 0px 0px. If root is specified, the value of rootMargin can also be a percentage.

As shown in the figure, the blue area is the crossover area, and the rootMargin is the area that enlarges or Narrows the judgment of the root element. As shown in the figure, when the viewport (gray area) slides to the blue boundary below, it touches the crossover area, and a callback function will be triggered. You would have to touch the black target to count as touching the cross area and trigger the callback.

  • Threshold: a number between 0 and 1 indicating the percentage that should be visible before triggering. It can also be an array of numbers to create multiple trigger points, also known as thresholds. If no value is passed in by the constructor, the default value is 0.

Threshold: [0.1, 0.2, 0.3, 0.5], when the viewport enters 10% of the crossover area. Both trigger callbacks (e.g. red bars)

  • TrackVisibility: A Boolean value that indicates whether the current observer will track the target visibility of changes, the default is false, note that the visibility is not here refers to the target element and whether the root element intersection, whether it is the view visible (within the viewport), the analysis before we have already done, if this value is set to false, or is not set, Then the callback function parameter IntersectionObserverEntry isVisible attributes will always return false.

  • Delay: A number, namely, the delay in executing the callback function in milliseconds. If trackVisibility is set to true, the minimum value is set to 100, otherwise an error will be reported.

Callback: A callback function that is triggered when visibility changes

Proportion when the element is visible after more than a specified threshold, is called a callback function, the callback function accepts two parameters: the store IntersectionObserverEntry object instances of arrays and viewer (optional)

The first parameter IntersectionObserverEntry

  • BoundingClientRect: An object containing the return value of the getBoundingClientRect() method of the target element.

  • IntersectionRatio: An object containing the return value of getBoundingClientRect(), where the target element intersects with the root element.

  • IntersectionRect: 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.

  • IsIntersecting: If the target element intersects the root element, it returns true. If isIntersecting is true, then the target element has reached at least one of the thresholds specified in the thresholds attribute. If it is false, The target element is not visible within the given threshold.

  • IsVisible: For this property to take effect, the options parameter passed in when using the constructor to generate the observer instance must be set to true, and the delay must be greater than 100. Otherwise the property will always return false.

Indicates whether the target DOM is in the viewport region, that is, whether it is currently visible. True if it is visible, even if the target’s transparency is 0.

  • RootBounds: An object containing the return value of the getBoundingClientRect() method of the root element.

  • Target: : The observed target element, which is a DOM node. In cases where the observer contains multiple targets, this is an easy way to determine which target element triggered this intersection change.

  • Time: This property provides the time, in milliseconds, from the time the observer was first created to the time this intersection change was triggered. This way, you can track how long it takes the observer to reach a particular threshold. This property provides a new time even if the target is later scrolled into the view again. This can be used to track when the target element enters and leaves the root element, as well as the interval between the two threshold triggers.

BoundingClientRect, intersectionRatio, and rootBounds are expanded

  • Bottom: Distance from the bottom of the element to the top of the page
  • Left: The distance between the left side of the element and the left side of the page
  • Right: The distance from the right side of the element to the left side of the page
  • Top: The distance from the top of the element to the top of the page
  • Width: the width of the element
  • Height: The height of the element
  • X: Equal to left, the distance from the left side of the element to the left side of the page
  • Y: Equivalent to top, the distance from the top of the element to the top of the page

Use a diagram to show these properties, especially the right and bottom properties, which are not the same as position in CSS

The second argument IntersectionObserver observes the instance object

The observer instance contains the following properties above it

  • root
  • rootMargin
  • thresholds
  • trackVisibility
  • delay

The options object is passed in when we create the observer instance, except that the options object is optional. The properties of the observer instance will use the options object passed in, and the default values will be used if the observer instance is not passed. The attribute threshold in options is singular, and thresholds obtained by our instance is plural

It is important to note that all properties are read-only, meaning that once an observer is created, its configuration cannot be changed, so a given observer object can only be used to listen for specific changes in the visible region.

Simple demo

You can debug yourself to play

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .root {
      height: 400px;
      width: 400px;
      border: 1px solid black;
      overflow-y: scroll;
    }
    .root .box {
      position: relative;
      width: 400px;
      height: 900px;
    }
    .root .box .target{
      /* opacity: 0; */
      position: absolute;
      bottom: 0;
      width: 50px;
      height: 50px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div class="root">
    <div class="box">
      <div class="target"></div>
      <div ></div>
    </div>
  </div>
  <script>
    ((doc) => {
      let n = 0
      //获取目标元素
      const target = doc.querySelector(".target")
      //获取根元素
      const root = doc.querySelector(".root")
      //回调函数
      const callback = (entries, observer) => {
        n++
        console.log(`🐴🐴~ 执行了 ${n} 次callback`);
        console.log('🐴🐴~ entries:', entries);
        console.log('🐴🐴~ observer:', observer);
      };
      //配置对象
      const options = {
        root: root,
        rootMargin: '0px 0px 100px 0px',
        threshold: [0.1, 0.3, 0.5, 0.8, 1],
        trackVisibility: true,
        delay: 100
      };
      //创建观察器
      const myObserver = new IntersectionObserver(callback, options);
      //开始监听目标元素
      myObserver.observe(target);
      console.log('🐴🐴~ myObserver:', myObserver);
    })(document)
  </script>
</body>
</html>
Copy the code

Observer instance method

  1. observe
 const myObserver = new IntersectionObserver(callback, options);
 myObserver.observe(target);
Copy the code

Take a target element as a parameter. It makes sense that after we create the observer instance, we manually call the Observe method to tell it to start monitoring the target element.

You can configure to listen on multiple target elements in the same observer object

The target2 element is automatically monitored by code, whereas the target1 element is monitored after clicking the Observe button. As you can see from the GIF, when I clicked the Observe button, our entries array contained two pieces of data. As mentioned above, we can determine the target element by the target attribute

  1. unobserve
 const myObserver = new IntersectionObserver(callback, options);
 myObserver.observe(target);
 myObserver.unobserve(target)
Copy the code

Taking a target element as an argument, we manually call the unobserve method to stop listening on the specified target element when we don’t want to listen on an element. As you can see from the GIF, when we click the unobserve button, the two data sets become one, indicating that target1 is no longer monitored.

  1. disconnect
 const myObserver = new IntersectionObserver(callback, options);
 myObserver.disconnect()
Copy the code

When we do not want to monitor any of the target elements, we need to manually call the Disconnect method to stop listening. As can be seen from the GIF, when we click the Disconnect button, the console will no longer output log, indicating that monitoring has been stopped. You can start monitoring again through Observe.

  1. takeRecords

Returns all the observable IntersectionObserverEntry array of objects, less application scenario.

Instead of executing immediately when an interaction is observed, the callback function is executed asynchronously during idle periods using requestIdleCallback, but also provides a takeRecords method for synchronous invocation.

If the asynchronous callback is executed first, we return an empty array when we call the synchronous takeRecords method. Similarly, if all observer instances have been obtained via takeRecords, the callback function will not be executed.

Other Points for attention

Under what circumstances is the callback function configured by the constructor IntersectionObserver invoked?

The callback function configured by the constructor IntersectionObserver may be called in the following cases

  • ObserverThe first time you listen on the target element.
  • Executed when the target element intersects the root element.
  • When the size of the intersection of two elements changes.

No matter whether the target element intersects with the root element or not, the callback function will be triggered once when we monitor the target element for the first time. Therefore, do not write the logical code directly in the callback function, but try to judge by isIntersecting or intersectionRect before executing the logical code

How is the visibility of the page monitored

The visibility of the page can be obtained through document.visibilityState or Document. hidden. Page visibility can pass the document change visibilitychange to listen.

Visibility and cross observation

Opacity: 0, visibility: Hidden and covering the target element with other elements will not affect the monitoring of the cross observer, that is, the result of isIntersecting attribute will not be affected, but the result of isVisible attribute will be affected. If the element is set to display: None, it will not be detected. Of course, not only these attributes affect the visibility of elements, but also include position, margin, clip, etc..

Calculation of intersection

All areas are treated as a rectangle by the Intersection Observer API. If the element is irregular, the shape will be treated as the smallest rectangle containing all of the region of the element. Similarly, if the element’s intersection part is not a rectangle, it will be treated as the smallest rectangle containing all of its intersection region.

How do I know if the target element comes from above or below the viewport

The scrolling direction of the target element can also be determined by the principle that the root element’s entry.rootbound.y (root container top) is fixed, So we only need to compute entry. BoundingClientRect. Y. (top of the target element) and entry, rootBounds. The size of the y, when triggered the callback function, we record the position at that time, If entry. BoundingClientRect. Y > entry. RootBounds. J y, that is at the bottom of the viewport, so when the next target element is visible, we know the target element time see the bottom of the mouth, and vice versa.

let wasAbove = false; function callback(entries, observer) { entries.forEach(entry => { const isAbove = entry.boundingClientRect.y > entry.rootBounds.y; \\ if (entry.isIntersecting) { if (wasAbove) { // Comes from top } } wasAbove = isAbove; }); }Copy the code

Application scenarios

1 Infinite scroll

(In this example, if you swipe the page quickly, the page will feel stuck, and if you scroll down again, you will not be able to scroll indefinitely. This is actually because we set the delay time, so long as we set trackVisibility to false, we can clear the delay. However, this can cause performance problems in real development, because if users keep scrolling down quickly, they will continue to request, so they have to throttle according to the actual situation.)

<template> <div> <div class="box"> <div class="vbody" V -for='item in list' :key='item'> Content area {{item}}</div> <div class="reference" ref='reference'></div> </div> </div> </template> <script> export default { name: '', data () { return { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] } }, methods: {intersectionObserver () {let n = 10 const reference = this.$refs.reference const callback = (entries) => {let n = 10 const reference = this. Console. log(entries) const myEntry = entries[0] if (myentry.isintersecting) {console.log(' 🚀🚀~ triggered wireless scrolling and started simulating request for data ${n} ') N++ this.list.push(n)}} // configure object const options = {root: null, rootMargin: '0px 100px 0px 0px', threshold: [0, 1], trackVisibility: true, delay: 100} // Const myObserver = new IntersectionObserver(callback, Observe myobserver. observe(reference)}}, mounted () { this.intersectionObserver() } } </script> <style scoped lang="less"> * { margin: 0; padding: 0; box-sizing: border-box; } .reference { width: 100%; visibility: hidden; } .vbody { width: 100%; height: 200px; // background-color: red; border: 2px solid darkblue; color: black; font-size: 40px; text-align: center; line-height: 200px; margin: 10px 0; } </style>Copy the code

2 Image preloading

Using the rootMargin attribute of Options, images can be loaded at the time when images are about to enter the visible area, which avoids performance problems caused by requesting a large number of images in advance, and avoids the problem that images are too late to be loaded until they enter the window

3 Buried point report

In general, we calculate whether an element is effectively seen by the user. It is not the element that triggers the burying point when it appears, but the element enters into a certain proportion of the area. We can set the options threshold to 0.5.

Case code difference is not big, the principle is the same, temporarily do not stick, there is a need can be sent again.

compatibility

Why are there two compatible diagrams? becausetrackVisibility 和 delayThe two properties belong toIntersectionObserver V2. So small partners in the use of time must pay attention to compatibility. Of course, there are compatible solutions, and that isintersection-observer-polyfill

Note:

This part from byte front ByteFE, author a tail prostitutes (mp.weixin.qq.com/s/rslnfBfcU… I have sorted out the content and added some personal notes for the purpose of learning.