82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import { ref, watch, onUnmounted, type Ref } from 'vue';
|
|
|
|
export function useAutoScroll(elementRef: Ref<HTMLElement | null>, options: {
|
|
threshold?: number;
|
|
} = {}) {
|
|
const { threshold = 80 } = options;
|
|
|
|
const isUserScrollingUp = ref(false);
|
|
const shouldAutoScroll = ref(true);
|
|
|
|
const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
|
|
const el = elementRef.value;
|
|
if (!el) return;
|
|
el.scrollTo({
|
|
top: el.scrollHeight,
|
|
behavior,
|
|
});
|
|
shouldAutoScroll.value = true;
|
|
};
|
|
|
|
const handleScroll = () => {
|
|
const el = elementRef.value;
|
|
if (!el) return;
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = el;
|
|
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
|
|
if (distanceFromBottom <= threshold) {
|
|
if (isUserScrollingUp.value) {
|
|
isUserScrollingUp.value = false;
|
|
shouldAutoScroll.value = true;
|
|
}
|
|
} else {
|
|
isUserScrollingUp.value = true;
|
|
shouldAutoScroll.value = false;
|
|
}
|
|
};
|
|
|
|
let observer: MutationObserver | null = null;
|
|
let timeout: NodeJS.Timeout | null = null;
|
|
|
|
watch(elementRef, (newEl, oldEl) => {
|
|
if (oldEl) {
|
|
oldEl.removeEventListener('scroll', handleScroll);
|
|
observer?.disconnect();
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
}
|
|
}
|
|
|
|
if (newEl) {
|
|
newEl.addEventListener('scroll', handleScroll, { passive: true });
|
|
|
|
observer = new MutationObserver(() => {
|
|
// Only auto-scroll if user hasn't scrolled up and is near bottom
|
|
if (!isUserScrollingUp.value && shouldAutoScroll.value) {
|
|
scrollToBottom('instant');
|
|
}
|
|
});
|
|
|
|
observer.observe(newEl, {
|
|
childList: true,
|
|
subtree: true,
|
|
characterData: true,
|
|
});
|
|
}
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
elementRef.value?.removeEventListener('scroll', handleScroll);
|
|
observer?.disconnect();
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
}
|
|
});
|
|
|
|
return {
|
|
scrollToBottom,
|
|
isUserScrollingUp, // expose for UI feedback (optional)
|
|
};
|
|
} |