useVirtualList
v0.0.14testeddemoVirtualize a large list with dynamically measured item sizes.
Rows render at their natural size: layout starts from estimateSize and is
corrected by a shared ResizeObserver, which fires after layout but before
paint, so corrections are not visible as flicker. Offsets come from a
Fenwick tree (O(log n) hot paths). When an item above the viewport changes
size — or the list is prepended to — the scroll position is compensated so
content does not jump; the compensation write is deferred until after the
DOM patch (still pre-paint) so it is never clamped by a stale wrapper
height. Supports vertical and horizontal (LTR) layouts, gap/paddings,
an external scrollElement, followOutput chat pinning, scrollTo with
nearest-edge 'auto' alignment, and SSR via initialContainerSize.
Non-goals (by design): window as scroller (element scrollers only),
RTL horizontal mode, reactive options (only the source and scrollElement
are reactive), pixel-perfect behavior: 'smooth' landings.
Examples
const messages = shallowRef<Message[]>([]);
const { list, containerProps, wrapperProps } = useVirtualList(messages, {
estimateSize: m => 52 + Math.ceil(m.text.length / 80) * 20,
getItemKey: m => m.id, // measurements survive prepend/reorder
followOutput: true, // stay pinned to the newest message
});
// <div v-bind="containerProps" style="height: 300px">
// <div v-bind="wrapperProps">
// <article v-for="item in list" :key="item.key" v-bind="item.props">
// {{ item.data.text }} <!-- natural height, measured automatically -->
// </article>
// </div>
// </div>// Fixed-size grid rows: the estimate is exact, measurement never corrects.
const { list } = useVirtualList(items, { estimateSize: 44, overscan: 10 });Demo
Signature
export function useVirtualList<T = unknown>(
source: MaybeRefOrGetter<readonly T[]>,
options: UseVirtualListOptions<T> ={ ... }Type Parameters
T= unknownParameters
| Parameter | Type | Description |
|---|---|---|
source | MaybeRefOrGetter<readonly T[]> | The full source array (may be reactive) |
options? | UseVirtualListOptions<T> | Layout and behavior options |
Returns
UseVirtualListReturn<T>{ list, totalSize, range, isScrolling, containerRef, containerProps, wrapperProps, measureElement, scrollTo, scrollToOffset, getOffsetForIndex, updateLayout, remeasure }| Property | Type | Description |
|---|---|---|
list | ComputedRef<Array<UseVirtualListItem<T>>> | Items in the current window, with layout offsets and spreadable props. |
totalSize | ComputedRef<number> | Full content size along the scroll axis (paddings and gaps included), px. |
range | Readonly<ShallowRef<UseVirtualListRange>> | Current rendered index window [start, end), overscan included. |
isScrolling | Readonly<ShallowRef<boolean>> | — |
containerRef | ShallowRef<HTMLElement | null> | Scroll container element — bind via containerProps or use directly. |
containerProps | UseVirtualListContainerProps | Props to bind on the scrolling container element. |
wrapperProps | ComputedRef<{ style: CSSProperties }> | Reactive props to bind on the inner wrapper (sizer) element. |
measureElement | (el: unknown) => void | Row measurer — already wired into item.props.ref; exposed for custom layouts. |
scrollTo | (index: number, options?: UseVirtualListScrollToOptions) => void | Scroll the container so the item at index satisfies the alignment. |
scrollToOffset | (offset: number, options?: { behavior?: ScrollBehavior }) => void | — |
getOffsetForIndex | (index: number, align?: UseVirtualListAlign) => number | Scroll offset that would satisfy align for index.
Reflects the layout as of the last flush (does not force a re-sync). |
updateLayout | () => void | Re-read the source and rebuild layout. Only needed after in-place
mutation of the source array (watch cannot observe those) — replacing
the array triggers this automatically, and scrollTo re-syncs a
same-tick replacement on its own. |
remeasure | (index?: number) => void | Drop cached measurements (all, or one index) and re-measure live rows. Useful when row content changes without a resize the observer would see. |