In the process of business development, we often encounter the need to pull down to load list data. This article shows you how to use the Intersection API to implement a simple pull-down data loading demo.

Traditional pull-down loading scheme

Most of the traditional pull-down loading schemes monitor scroll events, and then obtain the target element coordinates and relevant data, and then implement the corresponding. For example, here is a pull-down demo that relies on the data list container’s scrollHeight, scrollTop, and height implementations.

Code implementation

function App() {
  // Is used to record whether the request is currently in progress
  const loadingRef = useRef<boolean>(false);
  // List container
  const containerRef = useRef<HTMLDivElement>(null);
  const [dataList, setDataList] = useState([]);

  useEffect(() = >{ fetchData(); } []); useEffect(() = > {
    const { height } = containerRef.current.getBoundingClientRect();
    const scrollHeight = containerRef.current.scrollHeight;

    const onScroll = () = > {
      console.log('scrollHeight:', scrollHeight, 'scrollTop:', containerRef.current.scrollTop, 'height:', height);
      if (scrollHeight - containerRef.current.scrollTop - 1 <= height) {
        // Initiate a request when the container has been pulled to the bottomfetchData(); }}; containerRef.current.addEventListener('scroll', onScroll);

    return () = > {
      containerRef.current.removeEventListener('scroll', onScroll); }; } []);const fetchData = () = > {
    // Simulate a data request
    // If the request is currently in progress, return it directly
    if (loadingRef.current) return;
    // indicates that the request is currently in progress
    loadingRef.current = true;
    setTimeout(() = > {
      setDataList(_dataList= > {
        const dataList = [..._dataList];
        for (let i = 0; i < 20; i++) {
          dataList.push(Math.random());
        }

        return dataList;
      });
      loadingRef.current = false;
    }, 500);
  };

  return (
    <div ref={containerRef} className="list-container">
      {dataList.map(item => (
        <p className="list-item" key={item}>
          {item}
        </p>
      ))}
      <div className="loading">loading...</div>
    </div>
  );
}
Copy the code

Implementation effect

There is a problem

As we know, the occurrence of Scroll events is very intensive. In the callback function that listens for scroll events, we need to retrieve the scrollTop of the list container, which will lead to the occurrence of “rearrangement”. At this time, we need to make some additional tools for anti-shake or throttling to prevent performance problems.

/ / throttling
throttle(onScroll, 500);
Copy the code

As sharp-eyed students may have noticed, we did a -1 operation to determine whether the container has been rolled to the bottom.

If (scrollHeight - containerRef. Current. ScrollTop - < = 1 height) {/ / when the container has been to the bottom, the initiating fetchData (); }Copy the code

This is because on systems that use display scaling, scrollTop may provide a decimal number. As shown in the figure below, scrollHeight(1542) – scrollTop(1141.5999755859375) is not equal to the height of the container (400) when the container is rolled to the bottom.

So we need to make compatible processing accordingly.

IntersectionObserve version is loaded in a drop-down list

IntersectionObserve

IntersectionObserver provides a way to asynchronously observe whether a target element is visible in its ancestor element or top-level document window (viewport).

The usage of IntersectionObserver is very simple. All we need to do is define what we need to do if the DOM element’s visual state changes and which elements’ visual state we need to observe.

Next, we will look at intersectionObserver API in detail.

const intersectionObserver = new IntersectionObserver(callback, options?) ;
Copy the code

The IntersectionObserver constructor accepts two arguments.

callback

Callback is a callback function that takes two arguments after the visible state of the observed element has changed:

function callback(entries, observer?) = > {/ /... }Copy the code

Entries: a IntersectionObserverEntry array of objects. IntersectionObserverEntry object is used to describe the change of the visual state of the object being observed, has the following properties:

  • Entry.boundingclientrect: The boundary information of the observed element, equivalent to the result of the observed element calling getBoundingClientRect().
  • Entry. intersectionRatio: the ratio of the intersecting rectangle area of the observed element and container element to the total area of the observed element.
  • Entry. intersectionRect: specifies the boundary information of intersecting rectangles.
  • Entry. IsIntersecting: A Boolean value that indicates whether the observed element is visible. If true, it indicates meta-visibility; otherwise, it indicates non-visibility.
  • Entry.rootbounds: Boundary information for the container element, equivalent to the result of the container element’s call to getBoundingClientRect().
  • Entry. target: Reference to the element being observed.
  • Entry. time: indicates the current timestamp.

Observer: specifies the reference of IntersectionObserver instance.

options

Options is an optional parameter that can be passed in the following attributes:

  • Root: Specifies the container element, which defaults to the browser form element. The container element must be the ancestor node of the target element.
  • RootMargin: Used to expand or reduce the size of rootBounds, same as margin in the CSS. The default value is “0px 0px 0px 0px”.
  • Threshold: number or array of numbers, used to specify the threshold at which the callback function is executed, such as incoming[0, 0.2, 0.6, 0.8, 1], and every increase or decrease of intersectionRatio of 0.2 will trigger the execution of the callback function. The default value is0. It should be noted that intersectionRatio may be inconsistent with the specified threshold when the callback function is executed because it is triggered asynchronously.

IntersectionObserver instance

The IntersectionObserver constructor will mount the IntersectionObserver instance with the IntersectionObserver attributes in options and assign four methods to the IntersectionObserver instance: IntersectionObserver IntersectionObserver

  • IntersectionObserver. Disconnect () : stop listening.
  • IntersectionObserver. Observe (targetElem) : to start listening to an element visual status changes.
  • IntersectionObserver. TakeRecords () : returns all the observable IntersectionObserverEntry array of objects.
  • IntersectionObserver. Unobserve (targetElem) : stop listening to a target element.

The advantage of Intersection computes

The callback function passed in the intersectionObserver constructor will only be executed after the visible state of the observed element changes, which effectively solves the performance bottleneck of the traditional viewing scheme.

Implementation approach

When we implement the pull-down loading function, when the data list is not finished loading, we usually place a loading component at the end of the data list, indicating that the data list has more data and is being loaded. The Intersection version can be pulled down using the loading component’s visual state and the Intersection API.

Code implementation

Function App() {const loadingRef = useRef< Boolean >(false); // loading div const loadingDivRef = useRef<HTMLDivElement>(null); const [dataList, setDataList] = useState([]); useEffect(() => { fetchData(); } []); useEffect(() => { let intersectionObserver = new IntersectionObserver(function (entries) { if (Entries [0]. IntersectionRatio > 0) {// intersectionRatio greater than 0 indicates that the monitored element changes from invisible to visible and fetchData() is requested; }}); / / to monitor Loading div visibility intersectionObserver. Observe (loadingDivRef. Current); return () => { intersectionObserver.unobserve(loadingDivRef.current); intersectionObserver.disconnect(); intersectionObserver = null; }; } []); Const fetchData = () => {// If (loadingref.current) return; Loadingref.current = true; setTimeout(() => { setDataList(_dataList => { const dataList = [..._dataList]; for (let i = 0; i < 20; i++) { dataList.push(Math.random()); } return dataList; }); loadingRef.current = false; }, 500); }; return ( <div className="list-container"> {dataList.map(item => ( <p className="list-item" key={item}> {item} </p> ))} <div ref={loadingDivRef} className="loading"> loading... </div> </div> ); }Copy the code

Implementation effect