We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文使用 React Hooks 构建一个监听 DOM 元素曝光的 Custom Hooks,主要功能是监听 DOM 元素是否在规定内曝光,从而可以完成曝光打点。
过去我们监听 DOM 元素都是通过监听 scroll 事件来监听目标元素是否在可视区,这样我们需要获取目标元素的一些数据。这样似乎很麻烦,而通过 Intersection Observer 来完成监听,更加方便、友好。
MDN: IntersectionObserver 接口 (从属于 Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
其他相关概念可见 谈谈 IntersectionObserver 懒加载 、 IntersectionObserver API 使用教程
简单来说这个对象会观察目标元素,当目标元素与它的祖先元素或者可视区发生交叉时,会触发回调函数。
通过这个对象我们可以监听需要曝光打点的元素,更方便的实现打点。同时我们不需要监听 scroll 事件以及相对应的 DOM 操作,减少了性能的开销。
为了能够在不同的场景中使用,我们可以将其封装成 HOOKS,更方便的调用该方法
// useIntersectionObserver.ts // 定义参数函数类型以及返回值类型 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 为需要监听的元素列表。setList做为UseIntersectionObserver函数的返回值,可以让调用者修改需要监听的 list const [list, setList] = useState<ObserverList>(observerList) // intersectionObserver: 观察者对象 let intersectionObserver: IntersectionObserver | null = null // ... return [setList] } const useIntersectionObserver = UseIntersectionObserver export default useIntersectionObserver
UseIntersectionObserver 函数参数:
UseIntersectionObserver 返回值:
定义 observeExposure 函数
// UseIntersectionObserver const observeExposure = useCallback((list: ObserverList) => {}, [])
使用 useCallback 减少不必要的重复函数声明
判断浏览器环境以及 list 的是否为空
if (typeof IntersectionObserver === 'undefined') { throw new Error('Current browser does not support IntersectionObserver ') } if (list.length === 0) return
构造新的观察者实例
// 当观察者存在时销毁该对象 intersectionObserver && intersectionObserver.disconnect() // 构造新的观察者实例 intersectionObserver = new IntersectionObserver(entries => { // 保存本次监听被曝光的元素 let activeList: NumberList = [] // 递归每一个本次被监听对象,如果按照曝光条件出现在可视区,则调用callback函数,并且取消监听 entries.forEach(entrie => { // 找出本次被监听对象在list中的索引 const index = Array.from(list).findIndex( item => item.current === entrie.target ) // 防止意外发生 if (index === -1) return // isIntersecting是每个被监听的元素所自带的属性,若为ture,则表示被曝光 // 并且未被曝光过 if (entrie.isIntersecting) { // 保存本次曝光元素索引 activeList.push(index) // 解除观察, 若需要无限观察则不取消监听 !infinite && intersectionObserver && intersectionObserver.unobserve(list[index].current) } }) // callback函数 activeList.length > 0 && callback(activeList) }, opt)
使用 intersectionObserver 监听 list 中的元素
// 递归观察每一个元素 list.forEach(item => { item.current && intersectionObserver && intersectionObserver.observe(item.current) // 可以兼容直接传入DOM节点。 // if((<React.RefObject<any>>item).current) { // intersectionObserver.observe((<React.RefObject<any>>item).current) // } else if ((<HTMLElement>item)) { // intersectionObserver.observe((<HTMLElement>item)) // } })
useEffect(() => { observeExposure(list) // 当 umount 时取消链接 return () => { intersectionObserver && intersectionObserver.disconnect() } }, [list])
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 由被观察目标所组成的数组,数组项是由React.createRef构建出来的对象 * @param callback 当目标元素被曝光所需要触发的函数,该函数接受一个参数indexList,由被曝光元素在observerList数组中的索引组成 * @param infinite 是否持续观察目标元素,默认值为false。(因为曝光打点一般只需上报一次) * @param opt 可以自定义曝光条件(值的构成参考MDN),默认为{ threshold: [1] },只有当目标元素完全暴露在可视区内才触发回调 */ function UseIntersectionObserver( observerList: ObserverList, callback: CallbackFunction, infinite: boolean = false, opt: IntersectionObserverInit = {} ): ResultType { // list 为需要监听的元素列表。setList做为UseIntersectionObserver函数的返回值,可以让调用者修改需要监听的 list const [list, setList] = useState<ObserverList>(observerList) // intersectionObserver: 观察者对象 let intersectionObserver: IntersectionObserver | null = null const observeExposure = useCallback((list: ObserverList) => { if (!IntersectionObserver) { throw new Error('Current browser does not support IntersectionObserver ') } if (list.length === 0) return // 当观察者存在时销毁该对象 intersectionObserver && intersectionObserver.disconnect() // 构造新的观察者实例 intersectionObserver = new IntersectionObserver(entries => { // 保存本次监听被曝光的元素 let activeList: NumberList = [] // 递归每一个本次被监听对象,如果按照曝光条件出现在可视区,则调用callback函数,并且取消监听 entries.forEach(entrie => { // 找出本次被监听对象在list中的索引 const index = Array.from(list).findIndex( item => item.current === entrie.target ) // 防止意外发生 if (index === -1) return // isIntersecting是每个被监听的元素所自带的属性,若为ture,则表示被曝光 // 并且未被曝光过 if (entrie.isIntersecting) { // 保存本次曝光元素索引 activeList.push(index) // 解除观察, 若需要无限观察则不取消监听 !infinite && intersectionObserver && intersectionObserver.unobserve(list[index].current) } }) // callback函数 activeList.length > 0 && callback(activeList) }, opt) list.forEach(item => { item.current && intersectionObserver && intersectionObserver.observe(item.current) // 可以兼容直接传入DOM节点。 // if((<React.RefObject<any>>item).current) { // intersectionObserver.observe((<React.RefObject<any>>item).current) // } else if ((<HTMLElement>item)) { // intersectionObserver.observe((<HTMLElement>item)) // } }) }, []) useEffect(() => { observeExposure(list) // 当 umount 时取消链接 return () => { intersectionObserver && intersectionObserver.disconnect() } }, [list]) return [setList] } const useIntersectionObserver = UseIntersectionObserver export default useIntersectionObserver
实现一个简单的商品列表曝光打点的案例
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 // 被监听元素的列表 const [refList, setRefList] = useState<React.RefObject<any>[]>([]) const callback = useCallback((indexList: number[]) => { console.log(indexList) }, []) // 调用 const [setList] = useIntersectionObserver(refList, callback) // 当refList发生改变时,调用我们的Hook返回的方法以更新需要监听的元素 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)
案例效果
从动图可以看出,当 card 被曝光时打印出索引值,而已经被曝光的值不会再次曝光。
本文完~ 更多文章。 我的掘金~ 如有疑问,欢迎指出~
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
本文使用 React Hooks 构建一个监听 DOM 元素曝光的 Custom Hooks,主要功能是监听 DOM 元素是否在规定内曝光,从而可以完成曝光打点。
过去我们监听 DOM 元素都是通过监听 scroll 事件来监听目标元素是否在可视区,这样我们需要获取目标元素的一些数据。这样似乎很麻烦,而通过 Intersection Observer 来完成监听,更加方便、友好。
Intersection Observer
其他相关概念可见 谈谈 IntersectionObserver 懒加载 、 IntersectionObserver API 使用教程
简单来说这个对象会观察目标元素,当目标元素与它的祖先元素或者可视区发生交叉时,会触发回调函数。
通过这个对象我们可以监听需要曝光打点的元素,更方便的实现打点。同时我们不需要监听 scroll 事件以及相对应的 DOM 操作,减少了性能的开销。
Intersection Observer 在 React 中的实践
为了能够在不同的场景中使用,我们可以将其封装成 HOOKS,更方便的调用该方法
1. 定义 useIntersectionObserver 函数
UseIntersectionObserver 函数参数:
UseIntersectionObserver 返回值:
2. 实现监听
使用 useCallback 减少不必要的重复函数声明
3. 当每次被暴露的 setList 被调用时会使 list 被改变,此时需要重新监听
4. 完整代码实现
使用案例
从动图可以看出,当 card 被曝光时打印出索引值,而已经被曝光的值不会再次曝光。
本文完~
更多文章。 我的掘金~
如有疑问,欢迎指出~
The text was updated successfully, but these errors were encountered: