95 lines
2.3 KiB
JavaScript
95 lines
2.3 KiB
JavaScript
import { onUnmounted, watch } from 'vue'
|
|
|
|
export function useScrollWatcher({
|
|
target, // 接收外部传入的 ref
|
|
thresholdPercent = 0.05,
|
|
delay = 0,
|
|
onReachTop,
|
|
onReachBottom,
|
|
} = {}) {
|
|
if (!target)
|
|
throw new Error('target 必须传入一个 ref')
|
|
|
|
let lastScrollTop = 0
|
|
let timeout = null
|
|
const topTriggered = { value: false }
|
|
const bottomTriggered = { value: false }
|
|
|
|
const handleScroll = (e) => {
|
|
const el = e.target
|
|
const scrollTop = el.scrollTop
|
|
const scrollHeight = el.scrollHeight
|
|
const clientHeight = el.clientHeight
|
|
const scrollRange = scrollHeight - clientHeight
|
|
if (scrollRange <= 0)
|
|
return
|
|
|
|
const isScrollingUp = scrollTop < lastScrollTop
|
|
const isScrollingDown = scrollTop > lastScrollTop
|
|
lastScrollTop = scrollTop
|
|
|
|
const topThreshold = scrollRange * thresholdPercent
|
|
const bottomThreshold = scrollRange * thresholdPercent
|
|
|
|
clearTimeout(timeout)
|
|
timeout = setTimeout(() => {
|
|
if (isScrollingUp && scrollTop <= topThreshold && !topTriggered.value) {
|
|
onReachTop?.()
|
|
topTriggered.value = true
|
|
}
|
|
else if (scrollTop > topThreshold) {
|
|
topTriggered.value = false
|
|
}
|
|
|
|
if (isScrollingDown && scrollHeight - scrollTop - clientHeight <= bottomThreshold && !bottomTriggered.value) {
|
|
onReachBottom?.()
|
|
bottomTriggered.value = true
|
|
}
|
|
else if (scrollHeight - scrollTop - clientHeight > bottomThreshold) {
|
|
bottomTriggered.value = false
|
|
}
|
|
}, delay)
|
|
}
|
|
|
|
const bindScroll = (el) => {
|
|
if (!el)
|
|
return
|
|
el.addEventListener('scroll', handleScroll)
|
|
lastScrollTop = el.scrollTop
|
|
}
|
|
|
|
const unbindScroll = (el) => {
|
|
if (!el)
|
|
return
|
|
el.removeEventListener('scroll', handleScroll)
|
|
}
|
|
|
|
// 监听 ref
|
|
watch(target, (el, oldEl) => {
|
|
if (oldEl)
|
|
unbindScroll(oldEl)
|
|
if (el)
|
|
bindScroll(el)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (target.value)
|
|
unbindScroll(target.value)
|
|
clearTimeout(timeout)
|
|
})
|
|
|
|
const scrollToTop = () => {
|
|
if (target.value)
|
|
target.value.scrollTop = 0
|
|
}
|
|
|
|
const scrollToBottom = () => {
|
|
if (target.value) {
|
|
const el = target.value
|
|
el.scrollTop = el.scrollHeight - el.clientHeight
|
|
}
|
|
}
|
|
|
|
return { scrollToTop, scrollToBottom }
|
|
}
|