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