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 } }