react 元素触底hooks封装

useScrollHitBottom.ts

typescript 复制代码
import React, { useEffect, useCallback, useMemo } from 'react'

interface IProps {
  loading: boolean
  onHitBottom: () => void
  container?: HTMLElement | React.RefObject<HTMLElement> | Window | null // 支持多种容器类型
  offset?: number // 触底偏移量
}

const useScrollHitBottom = ({
  loading,
  onHitBottom,
  container = window,
  offset = 100, // 默认距离底部100px触发
}: IProps) => {
  const targetContainer = useMemo(() => {
    if (!container) return window
    return 'current' in container ? container.current : container
  }, [container])

  // 2. 滚动事件回调(兼容所有容器)
  const handleScroll = useCallback(() => {
    if (!targetContainer) return

    // 3. 统一获取滚动参数(区分window和普通DOM)
    let scrollTop: number
    let scrollHeight: number
    let clientHeight: number

    if (targetContainer === window) {
      scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
      clientHeight = document.documentElement.clientHeight || window.innerHeight
    } else {
      scrollTop = targetContainer.scrollTop
      scrollHeight = targetContainer.scrollHeight
      clientHeight = targetContainer.clientHeight
    }

    const isHitBottom = scrollTop + clientHeight >= scrollHeight - offset
    const hasScrollBar = scrollHeight > clientHeight

    if (isHitBottom && hasScrollBar && !loading) {
      onHitBottom()
    }
  }, [targetContainer, loading, onHitBottom, offset])

  useEffect(() => {
    if (!targetContainer) return

    // 绑定滚动事件(window/document/DOM 都支持 addEventListener)
    const eventTarget = targetContainer === window ? document : targetContainer
    eventTarget.addEventListener('scroll', handleScroll)

    return () => {
      eventTarget.removeEventListener('scroll', handleScroll)
    }
  }, [targetContainer, handleScroll])
}

export default useScrollHitBottom

使用

javascript 复制代码
import { RESUME_CID } from '@/constant/resume'
import { Header, Keywords, Template, Promotion, Search, Menu, Resume } from './components'
import style from './index.module.scss'
import { useScrollHitBottom } from '@/hooks'
import { useRef } from 'react'
import { useAggregationStore } from '@/store/aggregation'
import { useParams } from 'react-router'

const AggregationV2 = () => {
  const { cid } = useParams()

  const divref = useRef<HTMLDivElement>(null)
  const [loading, setLoading] = useState(false)
  const [hitBottom, setHitBottom] = useState(false)

  const onHitBottom = () => {
    if (!loading) {
      console.log('触底了')
      setHitBottom(true)
    }
  }
  useScrollHitBottom({ loading, onHitBottom, container: divref.current })

  return (
    <div className={style.aggregationV2} ref={divref}>
      <div className={style.containerTop}>
        <Menu />
        <div className={style.containerTopBox}>
          <Header />
          <Search />
          <Keywords />
        </div>
      </div>
      {Number(cid) === RESUME_CID ? (
        <div className={style.container}>
          <Resume />
        </div>
      ) : (
        <div className={style.container}>
          <Promotion />
          <Template />
        </div>
      )}
    </div>
  )
}

export default AggregationV2

  
相关推荐
dly_blog21 分钟前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-194335 分钟前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')1 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
用户47949283569151 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我123451 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
用户47949283569152 小时前
给客户做私有化部署,我是如何优雅搞定 NPM 依赖管理的?
前端·后端·程序员
C_心欲无痕2 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun9892 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构
胡楚昊2 小时前
NSSCTF动调题包通关
开发语言·javascript·算法
熬夜敲代码的小N2 小时前
Vue (Official)重磅更新!Vue Language Tools 3.2功能一览!
前端·javascript·vue.js