preface
This article uses React Hooks to build a Custom hook that listens for DOM elements to be exposed.
In the past, we used to listen for DOM elements by listening for scroll events to see if the target element is in the viewable area, so we need to get some data about the target element. This seems cumbersome, but using Intersection Observer to do this is more convenient and friendly.
Intersection Observer
The MDN: IntersectionObserver interface (part of the IntersectionObserver API) 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.
Other relevant concepts can be seen in the discussion on IntersectionObserver lazy loading and IntersectionObserver API usage tutorial
Simply put, the object looks at the target element and fires a callback when the target element intersects with its ancestor or viewable area.
Through this object we can listen to the elements that need to be exposed to dot, easier to achieve dot. At the same time, we do not need to listen for scroll events and corresponding DOM operations, reducing performance overhead.
Intersection Observer practice in React
To be able to use it in different scenarios, we can wrap it as HOOKS to make it easier to call this method
1. Define the useIntersectionObserver function
// useIntersectionObserver.ts
// Define parameter function types and return value types
import { useState, useCallback, useEffect } from 'react'
type NumberList = number[]
type ObserverList = Array<React.RefObject<any>>
type CallbackFunction = (indexList: NumberList) = > void
type ResultType = [React.Dispatch<React.SetStateAction<React.RefObject<any> [] > >]function UseIntersectionObserver (
observerList: ObserverList,
callback: CallbackFunction,
infinite: boolean = false,
opt: IntersectionObserverInit = {}
) :ResultType {
// list is a list of elements to listen on. SetList, as the return value of UseIntersectionObserver, allows the caller to modify the list to be monitored
const [list, setList] = useState<ObserverList>(observerList)
// intersectionObserver: specifies the observer object
let intersectionObserver: IntersectionObserver | null = null
// ...
return [setList]
}
const useIntersectionObserver = UseIntersectionObserver
export default useIntersectionObserver
Copy the code
UseIntersectionObserver function parameters:
- ObserverList: An array of observed objects. The entries are objects constructed from React. CreateRef
- Callback: A function that needs to be triggered when the target element is exposed. This function takes an indexList argument consisting of the index of the exposed element in the observerList array
- Infinite: Whether to continuously observe the target element. The default value is false. (Because exposure is usually reported only once)
- Opt: You can customize exposure conditions (value composition reference MDN). The default is {threshold: [1]}. The callback is triggered only when the target element is fully exposed to the visible area
UseIntersectionObserver Return value:
- Returns an array whose first element is returned by useState of React.
2. Implement listening
Define the observeExposure function
// UseIntersectionObserver
const observeExposure = useCallback((list: ObserverList) = > {}, [])
Copy the code
Use useCallback to reduce unnecessary duplicate function declarations
Check whether the browser environment and list are empty
if(! IntersectionObserver) {throw new Error('Current browser does not support IntersectionObserver ')}if (list.length === 0) return
Copy the code
- The target major browsers already support the object, but some older browsers are still compatible
- Return when list is empty
Construct a new observer instance
// Destroy the object while the observer exists
intersectionObserver && intersectionObserver.disconnect()
// Construct a new observer instance
intersectionObserver = new IntersectionObserver(entries= > {
// Save the exposed elements of this listen
let activeList: NumberList = []
If the exposure condition is visible, the callback function will be called and the listener will be cancelled
entries.forEach(entrie= > {
// Find the index of the listener in the list
const index = Array.from(list).findIndex(
item= > item.current === entrie.target
)
// Prevent accidents
if (index === - 1) return
// isIntersecting is the inherent attribute of each monitored element. If true, it indicates exposure
// And has not been exposed
if (entrie.isIntersecting) {
// Save the exposure element index
activeList.push(index)
// Cancel the watch, if unlimited watch is required, do not cancel the listen! infinite && intersectionObserver && intersectionObserver.unobserve(list[index].current) } })/ / callback function
activeList.length > 0 && callback(activeList)
}, opt)
Copy the code
IntersectionObserver is used to listen on elements in the list
// Look at each element recursively
list.forEach(item= > {
item.current &&
intersectionObserver &&
intersectionObserver.observe(item.current)
// It is compatible with passing directly into DOM nodes.
// if((<React.RefObject<any>>item).current) {
// intersectionObserver.observe((
>item).current)
// } else if ((<HTMLElement>item)) {
// intersectionObserver.observe((
item))
// }
})
Copy the code
3. When the exposed setList is called each time, the list will be changed, and the listener needs to listen again
useEffect((a)= > {
observeExposure(list)
// Unlink while unmounting
return (a)= > {
intersectionObserver && intersectionObserver.disconnect()
}
}, [list])
Copy the code
4. Complete code implementation
import { useState, useCallback, useEffect } from 'react'
type NumberList = number[]
type ObserverList = Array<React.RefObject<any>>
type CallbackFunction = (indexList: NumberList) = > void
type ResultType = [React.Dispatch<React.SetStateAction<React.RefObject<any> [] > >]/** * UseIntersectionObserver * @param observerList An array of observed targets, The function that needs to be fired when the target element is exposed. This function takes an indexList, Consists of the index of the exposed element in the observerList array * @param Infinite continuously observes the target element. The default value is false. By default, {threshold: [1]} will trigger a callback */ only if the target element is fully exposed
function UseIntersectionObserver (
observerList: ObserverList,
callback: CallbackFunction,
infinite: boolean = false,
opt: IntersectionObserverInit = {}
) :ResultType {
// list is a list of elements to listen on. SetList, as the return value of UseIntersectionObserver, allows the caller to modify the list to be monitored
const [list, setList] = useState<ObserverList>(observerList)
// intersectionObserver: specifies the observer object
let intersectionObserver: IntersectionObserver | null = null
const observeExposure = useCallback((list: ObserverList) = > {
if (typeof IntersectionObserver === 'undefined') {
throw new Error('Current browser does not support IntersectionObserver ')}if (list.length === 0) return
// Destroy the object while the observer exists
intersectionObserver && intersectionObserver.disconnect()
// Construct a new observer instance
intersectionObserver = new IntersectionObserver(entries= > {
// Save the exposed elements of this listen
let activeList: NumberList = []
If the exposure condition is visible, the callback function will be called and the listener will be cancelled
entries.forEach(entrie= > {
// Find the index of the listener in the list
const index = Array.from(list).findIndex(
item= > item.current === entrie.target
)
// Prevent accidents
if (index === - 1) return
// isIntersecting is the inherent attribute of each monitored element. If true, it indicates exposure
// And has not been exposed
if (entrie.isIntersecting) {
// Save the exposure element index
activeList.push(index)
// Cancel the watch, if unlimited watch is required, do not cancel the listen! infinite && intersectionObserver && intersectionObserver.unobserve(list[index].current) } })/ / callback function
activeList.length > 0 && callback(activeList)
}, opt)
list.forEach(item= > {
item.current &&
intersectionObserver &&
intersectionObserver.observe(item.current)
// It is compatible with passing directly into DOM nodes.
// if((<React.RefObject<any>>item).current) {
// intersectionObserver.observe((
>item).current)
// } else if ((<HTMLElement>item)) {
// intersectionObserver.observe((
item))
// }
})
}, [])
useEffect((a)= > {
observeExposure(list)
// Unlink while unmounting
return (a)= > {
intersectionObserver && intersectionObserver.disconnect()
}
}, [list])
return [setList]
}
const useIntersectionObserver = UseIntersectionObserver
export default useIntersectionObserver
Copy the code
Use case
Implement a simple list of goods exposure dot case
import Card from 'components/goods-card/goods-card' import { connect } from 'react-redux' import { getSinglePromotionList } from '.. /.. /page_components/promotion/redux/creator' import React, { useEffect, useState, useCallback } from 'react' import useIntersectionObserver from 'page_components/promotion/useIntersectionObserver' const List = (props: { info: any; getData: Any}) => {const {info, getData} = props // List of listened elements const [refList, setRefList] = useState<React.RefObject<any>[]>([]) const callback = useCallback((indexList: Number []) => {console.log(indexList)}, []) // Call const [setList] = useIntersectionObserver(refList, UseEffect (() => {setList(refList)}, [refList]) useEffect(() => {setList(refList)}, [refList]) RefList useEffect(() => {const list: React.RefObject<any>[] = info.list.map(() => react.createref ()) setRefList(list)}, [info]) UseEffect (() => {getData()}, []) return (<div style={{display: 'flex', flexWrap: 'wrap' }}> {info.list.map((item: any, index: number) => ( <div ref={refList[index]} key={index}> <Card card={item} /> </div> ))} </div> ) } const mapStateToProps = (state: any) => { return { info: state.promotionStore.singlePromotionInfo, userInfo: state.userInfo } } const mapDispatchToProps = (dispatch: any) => { return { getData: () => dispatch(getSinglePromotionList(params, silence)) } } export default connect( mapStateToProps, mapDispatchToProps )(List)Copy the code
Effect of case
As you can see from the driven figure, the index value is printed when card is exposed, and the value that has been exposed is not exposed again.
In this paper, the ~
If you have any questions, please point out ~