InfiniteLoader
Wraps VirtualList and calls onLoadMore when the user scrolls within threshold pixels of the bottom (or top, or both).
Props
items
T[]
Current data array.
onLoadMore
() => Promise<void>
Called when more data is needed.
isLoading
boolean
Whether a load is in progress.
hasMore
boolean
Whether more data exists.
threshold
number · default: 200
Distance from edge (px) at which to trigger onLoadMore.
direction
'down' | 'up' | 'both' · default: 'down'
Which edge(s) trigger loading.
estimatedItemSize
number · default: 50
Estimated row height.
overscan
number · default: 3
Extra rows outside viewport.
keyField
string · default: 'id'
Row key field.
motionBlur
boolean · default: false
Apply a CSS blur that scales with scroll velocity while scrolling fast.
Slots
default
Scope: { item: T, index: number, style }
Row content.
loading-indicator
Scope: No scope
Custom loading spinner (shown at top/bottom depending on direction).
empty
Scope: No scope
Empty state.
Emits
scroll
Payload: Event
visible-range-change
Payload: { start: number; end: number }
Exposed API
scrollTo(index, align?, options?)
Scroll to an item. align is 'start' | 'center' | 'end' | 'auto'.
scrollToOffset(px, options?)
Scroll to a raw pixel offset.
getScrollElement()
Returns the element that actually scrolls — pair with VirtualScrollbar.
loaderRef.value?.scrollTo(index, align)
loaderRef.value?.scrollTo(index, 'start', { behavior: 'smooth' })
loaderRef.value?.scrollToOffset(px)
loaderRef.value?.getScrollElement()Example
<script setup lang="ts">
import { ref } from 'vue'
import { InfiniteLoader } from 'vue-virtual-scroller-kit'
interface Post {
id: number
title: string
}
const posts = ref<Post[]>([])
const isLoading = ref(false)
const hasMore = ref(true)
let page = 0
async function loadMore() {
if (isLoading.value || !hasMore.value) return
isLoading.value = true
try {
const res = await fetch(`/api/posts?page=${page}`)
const data: Post[] = await res.json()
posts.value = [...posts.value, ...data]
hasMore.value = data.length === 20
page++
} finally {
isLoading.value = false
}
}
await loadMore()
</script>
<template>
<InfiniteLoader
:items="posts"
:on-load-more="loadMore"
:is-loading="isLoading"
:has-more="hasMore"
:estimated-item-size="72"
style="height: 600px"
>
<template #default="{ item }">
<div class="post-row">{{ item.title }}</div>
</template>
<template #loading-indicator>
<div style="padding: 16px; text-align: center">Loading…</div>
</template>
</InfiniteLoader>
</template>