Skip to content

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

如有转载或 CV 的请标注本站原文地址