Skip to content

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.

ts
loaderRef.value?.scrollTo(index, align)
loaderRef.value?.scrollTo(index, 'start', { behavior: 'smooth' })
loaderRef.value?.scrollToOffset(px)
loaderRef.value?.getScrollElement()

Example

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