Skip to content

useScroll

作用

监听元素的滚动位置

原理

监听目标的 scroll 的事件,更新目标位置信息

源码

ts
import useRafState from '../useRafState'
import useLatest from '../useLatest'
import type { BasicTarget } from '../utils/domTarget'
import { getTargetElement } from '../utils/domTarget'
import useEffectWithTarget from '../utils/useEffectWithTarget'

type Position = { left: number; top: number }

export type Target = BasicTarget<Element | Document>
export type ScrollListenController = (val: Position) => boolean

function useScroll(
  target?: Target,
  shouldUpdate: ScrollListenController = () => true,
): Position | undefined {
  // 保存位置信息
  const [position, setPosition] = useRafState<Position>()

  // 保存 shouldUpdate 的最新值
  const shouldUpdateRef = useLatest(shouldUpdate)

  // 监听目标的 scroll 事件
  useEffectWithTarget(
    () => {
      // 获取目标元素
      const el = getTargetElement(target, document)

      // 如果目标元素不存在,则直接返回
      if (!el) {
        return
      }

      // 更新位置信息的方法
      const updatePosition = () => {
        let newPosition: Position

        // 如果目标元素是 document,则获取 document 的滚动位置
        if (el === document) {
          if (document.scrollingElement) {
            newPosition = {
              left: document.scrollingElement.scrollLeft,
              top: document.scrollingElement.scrollTop,
            }
          } else {
            // When in quirks mode, the scrollingElement attribute returns the HTML body element if it exists and is potentially scrollable, otherwise it returns null.
            // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/scrollingElement
            // https://stackoverflow.com/questions/28633221/document-body-scrolltop-firefox-returns-0-only-js
            newPosition = {
              left: Math.max(
                window.pageXOffset,
                document.documentElement.scrollLeft,
                document.body.scrollLeft,
              ),
              top: Math.max(
                window.pageYOffset,
                document.documentElement.scrollTop,
                document.body.scrollTop,
              ),
            }
          }
        } else {
          newPosition = {
            left: (el as Element).scrollLeft,
            top: (el as Element).scrollTop,
          }
        }
        if (shouldUpdateRef.current(newPosition)) {
          setPosition(newPosition)
        }
      }

      // 初始化位置信息
      updatePosition()

      // 监听 scroll 事件
      el.addEventListener('scroll', updatePosition)

      // 返回移除监听的方法
      return () => {
        el.removeEventListener('scroll', updatePosition)
      }
    },
    [],
    target,
  )

  return position
}

export default useScroll

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