useInViewport
作用
观察元素是否在可见区域,以及元素可见比例
原理
通过
Intersection Observer API
。并使用intersection-observer
这个npm
包进行polyfill
处理。
源码
ts
import 'intersection-observer'
import { useState } from 'react'
import type { BasicTarget } from '../utils/domTarget'
import { getTargetElement } from '../utils/domTarget'
import useEffectWithTarget from '../utils/useEffectWithTarget'
type CallbackType = (entry: IntersectionObserverEntry) => void
export interface Options {
rootMargin?: string
threshold?: number | number[]
root?: BasicTarget<Element>
callback?: CallbackType
}
function useInViewport(target: BasicTarget | BasicTarget[], options?: Options) {
// 解构配置项
const { callback, ...option } = options || {}
// 保存当前状态
const [state, setState] = useState<boolean>()
// 保存当前元素的可见比例
const [ratio, setRatio] = useState<number>()
// 监听元素是否在可见区域
useEffectWithTarget(
() => {
// 获取目标元素
const targets = Array.isArray(target) ? target : [target]
// 获取目标元素的 DOM 节点
const els = targets.map((element) => getTargetElement(element)).filter(Boolean)
// 如果目标元素不存在,则直接返回
if (!els.length) {
return
}
// 创建 IntersectionObserver 实例
const observer = new IntersectionObserver(
// 当目标元素进入或者离开可见区域时触发
(entries) => {
for (const entry of entries) {
setRatio(entry.intersectionRatio)
setState(entry.isIntersecting)
callback?.(entry)
}
},
{
...option,
root: getTargetElement(options?.root),
},
)
// 开始监听
els.forEach((el) => {
if (el) {
observer.observe(el)
}
})
// 组件卸载时停止监听
return () => {
observer.disconnect()
}
},
[options?.rootMargin, options?.threshold, callback],
target,
)
// 返回当前状态和可见比例
return [state, ratio] as const
}
export default useInViewport