preface

In this article you will learn:

  • IntersectionObserver APIHow to use and how to be compatible.
  • How to inReact HookTo achieve infinite scrolling.
  • How to correctly render lists of up to 10,000 elements.

Infinite pull-down loading allows users to scroll through large chunks of content. The idea is to keep loading new content as you scroll down.

When you use scrolling as your primary method of discovering data, it can keep your users on the page longer and increase engagement. With the popularity of social media, huge amounts of data are consumed by users. Wireless scrolling provides an efficient way for users to browse massive amounts of information without having to wait for pages to be preloaded.

How to build a good experience of infinite scrolling is a topic that every front-end will encounter whether it is a project or an interview.

Creating Infinite Scroll with 15 Elements

1. Early solutions

Early solutions to infinite scrolling relied on listening for scroll events:

function fetchData() {
  fetch(path).then(res => doSomeThing(res.data));
}

window.addEventListener('scroll', fetchData);
Copy the code

Then calculate various.scrolltop (),.offset().top and so on.

Writing one by hand is also very boring. And:

  • scrollEvents fire frequently, so we also need to manually throttle.
  • There are a lot of scrolling elementsDOM, easy to cause jam.

Then came the cross-observerIntersectionObserver API withVue,ReactAfter such a framework for data-driven views, a general scheme for infinite scrolling emerged.

2. Cross-observer:IntersectionObserver

const box = document.querySelector('.box'); const intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((item) => { if (item.isintersecting) {console.log(' Enter visible area '); }})}); intersectionObserver.observe(box);Copy the code

Key points: IntersectionObserver API is asynchronous and does not trigger with rolling synchronization of target elements, resulting in very low performance consumption.

2.1 IntersectionObserverEntryobject

Here’s a rough outline of what you need:

IntersectionObserverEntryobject

Callback function is invoked, it will pass an array, the array in each object is the current into the viewing area or leave the viewing area of the object (IntersectionObserverEntry object)

This object has many properties, the most common of which are:

  • target: The object element being observed is a DOM node object
  • isIntersecting: Indicates whether to enter the viewable area
  • intersectionRatio: The ratio of the intersection area to the target element, into the visible area, greater than 0, otherwise equal to 0

2.3 options

When invoking IntersectionObserver, besides passing a callback function, IntersectionObserver can also pass an Option object and configure the following attributes:

  • threshold: determines when the callback function is triggered. It is an array and each member is a threshold value, which defaults to [0], i.e., intersectionRatio triggers the callback function when it reaches 0. The user can customize the array. For example, [0, 0.25, 0.5, 0.75, 1] means that the callback is triggered when the target element is 0%, 25%, 50%, 75%, 100% visible.
  • root: The root element for viewing. The default is the viewport of the browser. You can also specify a specific element
  • rootMarginTo increase or decrease the size of the window, use the CSS definition method, 10px, 10px, 30px, 20px for top, right, bottom, and left
const io = new IntersectionObserver((entries) => { console.log(entries); }, {threshold: [0, 0.5], root: document.querySelector('.container'), rootMargin: "10px 10px 30px 20px",});Copy the code

2.4 observer

Observer. Observer (nodeone); NodeOne observer.observer (nodeTwo); // Observe nodeOne and nodeTwo observer.unobserve (nodeOne); // Stop observing nodeOne observer.disconnect (); // No nodes are observedCopy the code

3. How toReact HookThe use ofIntersectionObserver

Before looking at the Hooks version, look at the normal component version:

class SlidingWindowScroll extends React.Component { this.$bottomElement = React.createRef(); . componentDidMount() { this.intiateScrollObserver(); } intiateScrollObserver = () => {const options = {root: null, rootMargin: '0px', threshold: 0.1}; this.observer = new IntersectionObserver(this.callback, options); this.observer.observe(this.$bottomElement.current); } render() { return ( <li className='img' ref={this.$bottomElement}> ) }Copy the code

React 16.x introduced useRef to replace createRef to track DOM nodes. So let’s get started:

Principle 4.

Implement a component that displays a list of N items with a fixed window size of 15 elements: that is, there are only 15 DOM nodes on an infinitely scrolling N element at any one time.

  • usingrelative/absolutePosition to determine the roll position
  • Track tworef: top/bottomTo determine whether to scroll up/down render or not
  • Slice the data list, keeping up to 15 DOM elements.

5. useStateDeclare state variables

Let’s start writing the component SlidingWindowScrollHook:

const THRESHOLD = 15; const SlidingWindowScrollHook = (props) => { const [start, setStart] = useState(0); const [end, setEnd] = useState(THRESHOLD); const [observer, setObserver] = useState(null); // Other code... }Copy the code

1. useState

Const [property, method to manipulate property] = useState(default);Copy the code

2. Variable resolution

  • start: The first data in the currently rendered list, defaults to 0
  • end: The last data in the currently rendered list. Default is 15
  • observer: Indicates the current viewrefThe element

6. useRefdefined-traceDOMThe element

const $bottomElement = useRef();
const $topElement = useRef();
Copy the code

Normal infinite scrolling down only focuses on one DOM element, but since we’re rendering a fixed 15 DOM elements, we need to decide whether to scroll up or down.

7. Internal operation methods and correspondinguseEffect

Please eat with notes:

UseEffect (() => {// define observing intiateScrollObserver(); Return () => {// discard observation resetObservation()}},[end]) // // define observation const intiateScrollObserver = () => {const options = {root: null, rootMargin: '0px', threshold: 0.1}; const Observer = new IntersectionObserver(callback, If ($topelement.current) {observe.observe ($topelement.current); } if ($bottomElement.current) { Observer.observe($bottomElement.current); } // Set the initial value setObserver(Observer)} // Cross the specific callback to observe each node, Const callback = (entries, observer) => {entries.foreach ((entry, index) => { const listLength = props.list.length; // Scroll down, If (entry.isintersecting && entry.target.id === "bottom") {const maxStartIndex = listLength-1-threshold; // Current header index const maxEndIndex = listLength-1; Const newEnd = (end + 10) <= maxEndIndex? end + 10 : maxEndIndex; Const newStart = (end-5) <= maxStartIndex? end - 5 : maxStartIndex; SetStart (newStart) setEnd(newEnd)} // Scroll up, Id === "top") {const newEnd = end === THRESHOLD? THRESHOLD : (end - 10 > THRESHOLD ? end - 10 : THRESHOLD); Let newStart = start === 0? 0 : (start - 10 > 0 ? start - 10 : 0); SetStart (newStart) setEnd(newEnd)}}); } const resetObservation = () => {observer && observer. serve($bottomelement.current); observer && observer.unobserve($topElement.current); Const getReference = (index, isLastIndex) => {if (index === 0) return $topElement; if (isLastIndex) return $bottomElement; return null; }Copy the code

8. Render the interface

const {list, height} = props; Const updatedList = list.slice(start, end); // const lastIndex = updatedList. length-1; return ( <ul style={{position: 'relative'}}> {updatedList.map((item, index) => { const top = (height * (index + start)) + 'px'; Const refVal = getReference(index, index === lastIndex); Const id = index === 0? 'top' : (index === lastIndex ? 'bottom' : ''); Return (<li className=" Li-card "key={item.key} style={{top}} ref={refVal} ID ={ID}>{item.value}</li>); })} </ul> );Copy the code

9. How to use it

App.js:

import React from 'react'; import './App.css'; import { SlidingWindowScrollHook } from "./SlidingWindowScrollHook"; import MY_ENDLESS_LIST from './Constants'; </h1> <SlidingWindowScrollHook list={MY_ENDLESS_LIST} height={195}/> </div> ); } export default App;Copy the code

Define data constants.js:

Const MY_ENDLESS_LIST = [{key: 1, value: 'A'}, {key: 2, value: 'B'}, {key: 3, value: 'C'}, 45, value: 'AS' } ]Copy the code

SlidingWindowScrollHook.js:

import React, { useState, useEffect, useRef } from "react"; const THRESHOLD = 15; const SlidingWindowScrollHook = (props) => { const [start, setStart] = useState(0); const [end, setEnd] = useState(THRESHOLD); const [observer, setObserver] = useState(null); const $bottomElement = useRef(); const $topElement = useRef(); useEffect(() => { intiateScrollObserver(); return () => { resetObservation() } // eslint-disable-next-line react-hooks/exhaustive-deps },[start, End]) const intiateScrollObserver = () => {const options = {root: null, rootMargin: '0px', threshold: 0.1}; const Observer = new IntersectionObserver(callback, options) if ($topElement.current) { Observer.observe($topElement.current); } if ($bottomElement.current) { Observer.observe($bottomElement.current); } setObserver(Observer) } const callback = (entries, observer) => { entries.forEach((entry, index) => { const listLength = props.list.length; // Scroll Down if (entry.isIntersecting && entry.target.id === "bottom") { const maxStartIndex = listLength - 1 - THRESHOLD; // Maximum index value `start` can take const maxEndIndex = listLength - 1; // Maximum index value `end` can take const newEnd = (end + 10) <= maxEndIndex ? end + 10 : maxEndIndex; const newStart = (end - 5) <= maxStartIndex ? end - 5 : maxStartIndex; setStart(newStart) setEnd(newEnd) } // Scroll up if (entry.isIntersecting && entry.target.id === "top") { const newEnd =  end === THRESHOLD ? THRESHOLD : (end - 10 > THRESHOLD ? end - 10 : THRESHOLD); let newStart = start === 0 ? 0 : (start - 10 > 0 ? start - 10 : 0); setStart(newStart) setEnd(newEnd) } }); } const resetObservation = () => { observer && observer.unobserve($bottomElement.current); observer && observer.unobserve($topElement.current); } const getReference = (index, isLastIndex) => { if (index === 0) return $topElement; if (isLastIndex) return $bottomElement; return null; } const {list, height} = props; const updatedList = list.slice(start, end); const lastIndex = updatedList.length - 1; return ( <ul style={{position: 'relative'}}> {updatedList.map((item, index) => { const top = (height * (index + start)) + 'px'; const refVal = getReference(index, index === lastIndex); const id = index === 0 ? 'top' : (index === lastIndex ? 'bottom' : ''); return (<li className="li-card" key={item.key} style={{top}} ref={refVal} id={id}>{item.value}</li>); })} </ul> ); } export { SlidingWindowScrollHook };Copy the code

And a few styles:

.li-card {
  display: flex;
  justify-content: center;
  list-style: none;
  box-shadow: 2px 2px 9px 0px #bbb;
  padding: 70px 0;
  margin-bottom: 20px;
  border-radius: 10px;
  position: absolute;
  width: 80%;
}
Copy the code

Then you can take your time…

10. Compatibility processing

IntersectionObserver not compatible with Safari?

Don’t panic, we have a polyfill version

340,000 downloads a week, so go ahead, guys.

Project source Address: github.com/roger-hiro/…

Reference article:

  • Creating Infinite Scroll with 15 Elements
  • IntersectionObserve first try

❤️ Read three things

If you find this article inspiring, I’d like to invite you to do me three small favors:

  1. Like, so that more people can see this content (collection does not like, is a rogue -_-)
  2. Follow the public account “front end persuader” to share original knowledge from time to time.
  3. Look at other articles as well
  • 120 lines of code to implement a fully interactive drag upload component
  • 160 lines of code to achieve dynamic and cool visual charts – leaderboards
  • “King of data Visualization library” d3. js fast start to Vue application
  • “True ® Full Stack Road” Back-end guide to Web front-end development
  • “Vue Practice” 5 minutes for a Vue CLI plug-in
  • “Vue practices” arm your front-end projects
  • “Intermediate and advanced front-end interview” JavaScript handwritten code unbeatable secrets
  • The answer to the Vue question “Learn from source” that no interviewer knows
  • “Learn from the source” Vue source JS operations
  • The “Vue Practice” project upgrades vuE-CLI3 to correct posture
  • Why do you never understand JavaScript scope chains?