background

The purpose of writing this article is mainly to record my own learning. I was still unfamiliar with this API before. When I was doing performance optimization in the project recently, I took this listening API into consideration and wrote this article by referring to various materials and articles.

React is used in all use cases in this article

1. IntersectionObserver object

The IntersectionObserver interface provides a way to asynchronously observe the intersecting status of a target element with its ancestor element or top-level document viewport. Ancestor elements and viewports are called roots.

When a IntersectionObserver object is created, it is configured to listen on a given proportion of visible areas in the root. Once IntersectionObserver is created, its configuration cannot be changed, so a given observer object can only be used to monitor specific changes in the visible area. However, you can configure to listen on multiple target elements in the same observer object.

2, use,

2.1. Interface definition


declare var IntersectionObserver: {
    prototype: IntersectionObserver;
    new(callback: IntersectionObserverCallback, options? : IntersectionObserverInit): IntersectionObserver; };Copy the code

Create a listener and listen for the tag id


const observer = new IntersectionObserver(callback, options)

observer.observe(document.getElementById('id'))

Copy the code

2.3. IntersectionObserver parameters callback and options

2.3.1 the callback

The interface definition

A callback function is called when the element visibility ratio exceeds a specified threshold. The callback function has two parameters, enters and Observer


interface IntersectionObserverCallback {
    (entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;
}

Copy the code

Enters: a IntersectionObserverEntry object array, each threshold is triggered, are more or less have deviation with a specified threshold.

Observer: IntersectionObserver instance to be invoked

IntersectionObserverEntry object

Represents the crossover state between a target element and its root element container at a specific transition time. IntersectionObserverEntry instance as a IntersectionObserver entries parameters are passed to the callback function. In addition, these objects can only by calling IntersectionObserver. TakeRecords () to obtain.

/** This Intersection Observer API interface describes the intersection between the target element and its root container at a specific moment of transition. */
interface IntersectionObserverEntry {
    readonly boundingClientRect: DOMRectReadOnly;
    readonly intersectionRatio: number;
    readonly intersectionRect: DOMRectReadOnly;
    readonly isIntersecting: boolean;
    readonly rootBounds: DOMRectReadOnly | null;
    readonly target: Element;
    readonly time: DOMHighResTimeStamp;
}

Copy the code

attribute

  • boundingClientRectReturns the element containing the target (The element being detected) boundary informationDOMRectReadOnly. The calculation method of boundary andElement.getBoundingClientRect()The same.
  • intersectionRatioReturns the ratio of intersectionRect to boundingClientRect.
  • intersectionRectReturns a DOMRectReadOnly that describes the intersection region of the root and target elements.
  • isIntersectingReturns a Boolean, true if the target element intersects the root of the cross-region observer object. If true is returned, intersection is returnedtrue, do not intersect returnfalse
  • rootBoundsReturns aDOMRectReadOnlyUsed to describe roots in an observer of a cross region.
  • targetThe element being detected
  • timeReturns a timestamp that records the time from the time origin of IntersectionObserver to the time when the IntersectionObserver is triggered

2.3.2 options

An object that can be used to configure an observer instance. If options are not specified, the Observer instance defaults to using the document viewport as root, with no margin and a threshold of 0% (meaning that even a pixel change triggers a callback function).

The interface definition


interfaceIntersectionObserverInit { root? : Element | Document |null; rootMargin? :string; threshold? :number | number[];
}

Copy the code

Root: Listens for the Element’s ancestor Element Element object. If no value is passed in or the value is null, the top-level document window is used by default.

RootMargin: The rectangular offset added to root bounding box when calculating crossover can effectively reduce or expand the determination range of roots to meet the calculation needs. The value returned by this property may be different from the value specified when the constructor is called, so it may need to be changed to match internal requirements. All offsets can be expressed by pixel (px) or percentage (%). The default value is “0px 0px 0px 0px”.

Threshold: A list of thresholds, in ascending order, in which each threshold is the ratio of the cross region to the boundary region of the listening object. When any threshold for a listening object is crossed, a notification is generated (read as a callback). If no value is passed in by the constructor, the default value is 0.

2.4 Instance Method

IntersectionObserver. Disconnect () : make IntersectionObserver object to stop listening

IntersectionObserver. Observe () : make IntersectionObserver to start listening to a target element.

IntersectionObserver. TakeRecords () : returns all the observable IntersectionObserverEntry array of objects. 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.

IntersectionObserver. Unobserve () : make IntersectionObserver stop listens on a specific target element.

Using an example

This example listens for the bottom div tag, and when it listens in a window, prints a prompt. And stop listening on this div

const listData = Array.from({ length: 50 }, (_, i) = > i + 1 )

function ObserverTestPage() {

  const observeRef = useRef<HTMLDivElement | null> (null)

  useEffect(() = > {
    const callback: IntersectionObserverCallback = (enters) = > {
      for (const item of enters) {
        if (item.isIntersecting) {
          console.log('Listening DOM appears ====')
          // Stop listening to the DOM after the first occurrence
          observe.unobserve(item.target)
        }
      }
    }
    const observe = new IntersectionObserver(callback)

    observe.observe(observeRef)

    // Stop all listening when the component is destroyed
    return () = > observe.disconnect()
  }, [])

  return (
    <div className="ImageLazy">
      <div className="plac">
        {listData.map(v => <div key={v}>{v}</div>)}
      </div>
      <div ref={observeRef} className="observe">
        this is dom
      </div>
    </div>)}Copy the code

3. Application scenarios

3.1 Image lazy loading


import React, { useEffect, useRef } from 'react'
import { imgList } from './constant'
import './ImageLazy.less'

const listData = Array.from({ length: 50 }, (_, i) = > i + 1 )

function ObserverTestPage() {

  const observeRef = useRef<HTMLDivElement | null> (null)

  useEffect(() = > {
    const callback: IntersectionObserverCallback = (enters) = > {
      for (const item of enters) {
        const { isIntersecting, target }: IntersectionObserverEntry & { target: any } = item
        if (isIntersecting) {
          if (target.nodeName === 'IMG' && !target.src) {
            target.src = target.dataset.src
            observe.unobserve(target)
          }
        }
      }
    }
    const observe = new IntersectionObserver(callback)
    const imgTag = document.querySelectorAll('img[data-src]')
    imgTag.forEach(v= > observe.observe(v))

    // Stop all listening when the component is destroyed
    return () = > observe.disconnect()
  }, [])

  return (
    <div className="ImageLazy">
      <div className="plac">
        {listData.map(v => <div key={v}>{v}</div>)}
      </div>
      <div ref={observeRef} className="observe">
        this is dom
      </div>
      <div className="image-content">
        {imgList.map((v, i) => (
          <div key={i} className="image-wrap">
            <img data-src={v} alt="" />
          </div>
        ))}
      </div>
    </div>)}export default ObserverTestPage

Copy the code

3.2 Whether listening Elements appear in the viewport (React Implementation Case)

Here is ahooks notation useInViewport notation

type InViewport = boolean | undefined;

function isInViewPort(el: HTMLElement) :InViewport  {
  if(! el) {return undefined;
  }

  const viewPortWidth =
    window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  const viewPortHeight =
    window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
  const rect = el.getBoundingClientRect();

  if (rect) {
    const { top, bottom, left, right } = rect;
    return bottom > 0 && top <= viewPortHeight && left <= viewPortWidth && right > 0;
  }

  return false;
}

function useInViewport(target: BasicTarget) :InViewport {
  const [inViewPort, setInViewport] = useState<InViewport>(() = > {
    const el = getTargetElement(target);

    return isInViewPort(el as HTMLElement);
  });

  useEffect(() = > {
    const el = getTargetElement(target);
    if(! el) {return () = > {};
    }

    const observer = new IntersectionObserver((entries) = > {
      for (const entry of entries) {
        if (entry.isIntersecting) {
          setInViewport(true);
        } else {
          setInViewport(false); }}}); observer.observe(elas HTMLElement);

    return () = > {
      observer.disconnect();
    };
  }, [target]);

  return inViewPort;
}

Copy the code

4. Compatibility

Data source can I use

5, summary

In general, IntersectionObserver has good operability and can be used to deal with lazy loading of images and components, etc., in terms of detecting elements entering the viewport area. However, compatibility should be considered when using intersection-observer-polyfill.

reference

1.MDN-Intersection Observer

2. A magical Intersection Observer API

3. ahhoks-useInViewport

4. can i use