Skip to content

Custom Virtualization

useVirtualScroll() — the low-level composable that powers all components. Use it when you need to build a custom virtual container.

ts
type SizeProvider = number | ((index: number) => number)

function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualScrollReturn

Options

itemCount

number | Ref<number>

Total item count.

estimatedItemSize

SizeProvider | Ref<SizeProvider> · default: 50

Estimated item height: number or (index) => number. A Ref triggers a full rebuild when it changes.

overscan

number · default: 3

Extra items rendered outside the viewport.

getScrollElement

() => HTMLElement | null

Returns the scroll container.

pageMode

boolean · default: false

Use window as the scroll container. Read once, not reactive — see note below.

horizontal

boolean · default: false

Virtualize scrollLeft/clientWidth instead of scrollTop/clientHeight, RTL-safe via normalizeScrollLeft. Read once, not reactive — see note below.

motionBlur

boolean · default: false

Track scroll velocity and expose it as blurAmount (px). Off by default — zero cost when disabled.

pageMode and horizontal are captured from options once when the composable is set up — changing them on a live instance has no effect. If you need to switch axes at runtime, remount the component that calls useVirtualScroll (e.g. via :key).

Return value

visibleRange

Readonly<Ref<VisibleRange>>

{ start, end } — first and last visible item indices.

totalHeight

Readonly<Ref<number>>

Total scrollable size in px along the scroll axis (height, or width when horizontal).

offsetTop

(index: number) => number

Pixel offset of item at index along the scroll axis (top, or left when horizontal).

scrollTo

(index, align?, options?) => void

Scroll to item ('start' | 'center' | 'end' | 'auto'). options.behavior is 'auto' (default, instant) or 'smooth'.

scrollToOffset

(offset: number, options?) => void

Scroll to a raw pixel offset. Same options.behavior.

measureItem

(index, height) => void

Report a measured row height.

handleScroll

() => void

Manually trigger a visible-range recalculation.

blurAmount

Readonly<Ref<number>>

Current motion-blur radius in px. Always 0 unless the motionBlur option is enabled.

Example

vue
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useVirtualScroll } from 'vue-virtual-scroller-kit'

const ITEMS = Array.from({ length: 50_000 }, (_, i) => `Item ${i + 1}`)
const containerRef = ref<HTMLElement | null>(null)

const { visibleRange, totalHeight, offsetTop } = useVirtualScroll({
  itemCount: ITEMS.length,
  estimatedItemSize: 40,
  getScrollElement: () => containerRef.value,
})
</script>

<template>
  <div ref="containerRef" style="height: 500px; overflow-y: auto; position: relative">
    <div :style="{ height: `${totalHeight}px`, position: 'relative' }">
      <div
        v-for="i in visibleRange.end - visibleRange.start + 1"
        :key="visibleRange.start + i - 1"
        :style="{
          position: 'absolute',
          top: `${offsetTop(visibleRange.start + i - 1)}px`,
          width: '100%',
          height: '40px',
        }"
      >
        {{ ITEMS[visibleRange.start + i - 1] }}
      </div>
    </div>
  </div>
</template>