119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
// composables/useInfiniteScroll.ts
|
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
|
|
|
export default function useInfiniteScroll(
|
|
callback: () => Promise<void>,
|
|
elementId?: string
|
|
) {
|
|
const route = useRoute();
|
|
const isFetching = ref(false);
|
|
const infiniteScroll = ref<HTMLElement | Window | null>(null);
|
|
|
|
// Throttle function to limit scroll event frequency
|
|
const throttle = (func: (...args: any[]) => void, limit: number) => {
|
|
let lastFunc: ReturnType<typeof setTimeout>;
|
|
let lastRan: number;
|
|
return function (this: any, ...args: any[]) {
|
|
const context = this;
|
|
if (!lastRan) {
|
|
func.apply(context, args);
|
|
lastRan = Date.now();
|
|
} else {
|
|
clearTimeout(lastFunc);
|
|
lastFunc = setTimeout(() => {
|
|
if (Date.now() - lastRan >= limit) {
|
|
func.apply(context, args);
|
|
lastRan = Date.now();
|
|
}
|
|
}, limit - (Date.now() - lastRan));
|
|
}
|
|
};
|
|
};
|
|
|
|
const checkScrollPosition = () => {
|
|
if (isFetching.value || !infiniteScroll.value) return;
|
|
|
|
let scrollPosition: number;
|
|
let threshold: number;
|
|
let clientHeight: number;
|
|
let scrollHeight: number;
|
|
|
|
if (infiniteScroll.value === window) {
|
|
scrollPosition = window.scrollY || window.pageYOffset;
|
|
clientHeight = document.documentElement.clientHeight;
|
|
scrollHeight = document.documentElement.scrollHeight;
|
|
} else {
|
|
const el = infiniteScroll.value as HTMLElement;
|
|
scrollPosition = el.scrollTop;
|
|
clientHeight = el.clientHeight;
|
|
scrollHeight = el.scrollHeight;
|
|
}
|
|
|
|
threshold = scrollHeight - 100;
|
|
const currentPosition = scrollPosition + clientHeight;
|
|
|
|
if (currentPosition >= threshold) {
|
|
isFetching.value = true;
|
|
callback().finally(() => {
|
|
isFetching.value = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
// Throttled version of scroll handler
|
|
const throttledScrollHandler = throttle(checkScrollPosition, 200);
|
|
|
|
const handleTouchEnd = () => {
|
|
setTimeout(throttledScrollHandler, 100);
|
|
};
|
|
|
|
onMounted(() => {
|
|
console.info(route.name)
|
|
|
|
if (route.name == "hadithaSearch" || route.name == "hadithaLibrary") {
|
|
const targetElement = elementId
|
|
? document.getElementById(elementId)
|
|
: window;
|
|
|
|
if (!targetElement) {
|
|
console.warn(
|
|
`Element ${elementId || "window"} not found for infinite scroll`
|
|
);
|
|
return;
|
|
}
|
|
|
|
infiniteScroll.value = targetElement;
|
|
|
|
if (targetElement === window) {
|
|
window.addEventListener("scroll", throttledScrollHandler);
|
|
window.addEventListener("touchend", handleTouchEnd);
|
|
} else {
|
|
targetElement.addEventListener("scroll", throttledScrollHandler);
|
|
targetElement.addEventListener("touchend", handleTouchEnd);
|
|
}
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (route.name == "hadithaSearch" || route.name == "hadithaLibrary") {
|
|
if (!infiniteScroll.value) return;
|
|
|
|
if (infiniteScroll.value === window) {
|
|
window.removeEventListener("scroll", throttledScrollHandler);
|
|
window.removeEventListener("touchend", handleTouchEnd);
|
|
} else {
|
|
(infiniteScroll.value as HTMLElement).removeEventListener(
|
|
"scroll",
|
|
throttledScrollHandler
|
|
);
|
|
(infiniteScroll.value as HTMLElement).removeEventListener(
|
|
"touchend",
|
|
handleTouchEnd
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
return { isFetching };
|
|
}
|