useInfiniteScroll Composable
Composable for infinite scroll functionality with intersection observer
Code
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
interface UseInfiniteScrollOptions {
root?: HTMLElement | null;
rootMargin?: string;
threshold?: number;
hasMore: Ref<boolean>;
loadMore: () => Promise<void>;
}
export function useInfiniteScroll(
target: Ref<HTMLElement | null>,
options: UseInfiniteScrollOptions
) {
const loading = ref(false);
let observer: IntersectionObserver | null = null;
const handleIntersect = __TOKEN_23__ ([entry]: IntersectionObserverEntry[]) => {
__TOKEN_24__ (entry.isIntersecting && options.hasMore.value && !loading.value) {
loading.value = true;
try {
await options.loadMore();
} finally {
loading.value = false;
}
}
};
onMounted(() => {
__TOKEN_28__ (!target.value || !IntersectionObserver) return;
observer = new IntersectionObserver(handleIntersect, {
root: options.root,
rootMargin: options.rootMargin || '200px',
threshold: options.threshold || 0
});
observer.observe(target.value);
});
onUnmounted(() => {
__TOKEN_31__ (observer && target.value) {
observer.unobserve(target.value);
observer.disconnect();
}
});
return { loading };
}
// Usage example
// const posts = ref([]);
// const page = ref(1);
// const hasMore = ref(true);
// const loadMorePosts = async () => {
// const newPosts = await fetch(`/api/posts?page=${page.value}`).then(r => r.json());
// if (newPosts.length === 0) hasMore.value = false;
// posts.value.push(...newPosts);
// page.value++;
// };
// const loadMoreRef = ref<HTMLElement | null>(null);
// const { loading } = useInfiniteScroll(loadMoreRef, { hasMore, loadMore: loadMorePosts });