CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tanstack--react-virtual

Headless UI for virtualizing scrollable elements in React

Pending
Overview
Eval results
Files

virtualizer-engine.mddocs/

Virtualizer Core Engine

The core virtualization engine class that manages all aspects of virtual scrolling including item measurement, range calculation, scroll positioning, and DOM element lifecycle management.

Capabilities

Virtualizer Class

The main virtualization engine that handles all scrolling, measurement, and rendering calculations.

/**
 * Core virtualization engine for managing virtual scrolling
 * @template TScrollElement - Type of the scroll container (Element or Window)
 * @template TItemElement - Type of the virtualized item elements
 */
export class Virtualizer<TScrollElement extends Element | Window, TItemElement extends Element> {
  constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>);
  
  // Configuration
  setOptions(opts: VirtualizerOptions<TScrollElement, TItemElement>): void;
  
  // Virtual item management
  getVirtualItems(): Array<VirtualItem>;
  getVirtualIndexes(): Array<number>;
  calculateRange(): { startIndex: number; endIndex: number } | null;
  
  // Measurement and sizing
  measureElement(node: TItemElement | null | undefined): void;
  resizeItem(index: number, size: number): void;
  getTotalSize(): number;
  measure(): void;
  
  // Scroll control
  scrollToIndex(index: number, options?: ScrollToIndexOptions): void;
  scrollToOffset(toOffset: number, options?: ScrollToOffsetOptions): void;
  scrollBy(delta: number, options?: ScrollToOffsetOptions): void;
  
  // Position calculations
  getOffsetForIndex(index: number, align?: ScrollAlignment): [number, ScrollAlignment] | undefined;
  getOffsetForAlignment(toOffset: number, align: ScrollAlignment, itemSize?: number): number;
  getVirtualItemForOffset(offset: number): VirtualItem | undefined;
  
  // Element utilities
  indexFromElement(node: TItemElement): number;
}

Virtualizer Properties

The Virtualizer instance exposes several readonly properties for accessing current state:

interface VirtualizerState {
  /** The current scroll element being observed */
  scrollElement: TScrollElement | null;
  /** Reference to the target window object */
  targetWindow: (Window & typeof globalThis) | null;
  /** Whether the virtualizer is currently scrolling */
  isScrolling: boolean;
  /** Cache of measured virtual items */
  measurementsCache: Array<VirtualItem>;
  /** Current dimensions of the scroll container */
  scrollRect: Rect | null;
  /** Current scroll offset position */
  scrollOffset: number | null;
  /** Current scroll direction when scrolling */
  scrollDirection: ScrollDirection | null;
  /** Cache of DOM element references */
  elementsCache: Map<Key, TItemElement>;
  /** Current visible range of items */
  range: { startIndex: number; endIndex: number } | null;
}

Virtual Item Management

Methods for managing and accessing virtualized items:

/**
 * Get the current list of virtual items that should be rendered
 * @returns Array of VirtualItem objects representing visible items
 */
getVirtualItems(): Array<VirtualItem>;

/**
 * Get the indexes of items that need to be rendered (including overscan)
 * @returns Array of item indexes to render
 */
getVirtualIndexes(): Array<number>;

/**
 * Calculate the currently visible range of items
 * @returns Object with startIndex and endIndex, or null if no items visible
 */
calculateRange(): { startIndex: number; endIndex: number } | null;

Usage Examples:

import { Virtualizer } from '@tanstack/react-virtual';

// Create virtualizer instance
const virtualizer = new Virtualizer({
  count: 1000,
  getScrollElement: () => document.getElementById('scroll-container'),
  estimateSize: () => 50,
  scrollToFn: (offset, { behavior }, instance) => {
    instance.scrollElement?.scrollTo({ top: offset, behavior });
  },
  observeElementRect: (instance, cb) => {
    // ResizeObserver implementation
    const observer = new ResizeObserver(entries => {
      const entry = entries[0];
      if (entry) {
        cb({ width: entry.contentRect.width, height: entry.contentRect.height });
      }
    });
    if (instance.scrollElement) observer.observe(instance.scrollElement as Element);
    return () => observer.disconnect();
  },
  observeElementOffset: (instance, cb) => {
    // Scroll event implementation
    const handler = () => cb((instance.scrollElement as Element).scrollTop, true);
    instance.scrollElement?.addEventListener('scroll', handler);
    return () => instance.scrollElement?.removeEventListener('scroll', handler);
  }
});

// Get items to render
const virtualItems = virtualizer.getVirtualItems();
virtualItems.forEach(item => {
  console.log(`Render item ${item.index} at position ${item.start}`);
});

Measurement and Sizing

Methods for handling dynamic item sizing and measurement:

/**
 * Measure a DOM element and update the virtualizer's size cache
 * @param node - The DOM element to measure, or null to clean up disconnected elements
 */
measureElement(node: TItemElement | null | undefined): void;

/**
 * Manually resize a specific item by index
 * @param index - Index of the item to resize
 * @param size - New size of the item in pixels
 */
resizeItem(index: number, size: number): void;

/**
 * Get the total size of all virtualized content
 * @returns Total height (vertical) or width (horizontal) in pixels
 */
getTotalSize(): number;

/**
 * Force a complete remeasurement of all items
 * Clears the size cache and triggers recalculation
 */
measure(): void;

Usage Examples:

// Measure element during render
function VirtualItem({ virtualizer, item, data }) {
  return (
    <div
      data-index={item.index}
      ref={(node) => virtualizer.measureElement(node)}
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%',
        transform: `translateY(${item.start}px)`,
      }}
    >
      {data[item.index]}
    </div>
  );
}

// Manual resizing after content change
function updateItemContent(virtualizer: Virtualizer, index: number, newContent: string) {
  // Update content...
  
  // If we know the new size, update it directly
  const newSize = estimateContentHeight(newContent);
  virtualizer.resizeItem(index, newSize);
  
  // Or trigger complete remeasurement
  virtualizer.measure();
}

// Get container height for styling
function VirtualContainer({ virtualizer }) {
  const totalSize = virtualizer.getTotalSize();
  
  return (
    <div style={{
      height: `${totalSize}px`,
      position: 'relative',
    }}>
      {/* Virtual items */}
    </div>
  );
}

Scroll Control

Methods for programmatically controlling scroll position:

/**
 * Scroll to a specific item index
 * @param index - Index of the item to scroll to
 * @param options - Scroll behavior options
 */
scrollToIndex(index: number, options?: ScrollToIndexOptions): void;

/**
 * Scroll to a specific offset position
 * @param toOffset - Target scroll position in pixels
 * @param options - Scroll behavior options
 */
scrollToOffset(toOffset: number, options?: ScrollToOffsetOptions): void;

/**
 * Scroll by a relative amount
 * @param delta - Amount to scroll in pixels (positive = down/right, negative = up/left)
 * @param options - Scroll behavior options
 */
scrollBy(delta: number, options?: ScrollToOffsetOptions): void;

interface ScrollToIndexOptions {
  /** How to align the item within the viewport */
  align?: ScrollAlignment;
  /** Scroll behavior (smooth or instant) */
  behavior?: ScrollBehavior;
}

interface ScrollToOffsetOptions {
  /** How to align the offset within the viewport */
  align?: ScrollAlignment;
  /** Scroll behavior (smooth or instant) */
  behavior?: ScrollBehavior;
}

type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
type ScrollBehavior = 'auto' | 'smooth';

Usage Examples:

// Scroll to specific items
function scrollToTop(virtualizer: Virtualizer) {
  virtualizer.scrollToIndex(0, { align: 'start' });
}

function scrollToBottom(virtualizer: Virtualizer) {
  virtualizer.scrollToIndex(virtualizer.options.count - 1, { align: 'end' });
}

function scrollToMiddle(virtualizer: Virtualizer) {
  const middleIndex = Math.floor(virtualizer.options.count / 2);
  virtualizer.scrollToIndex(middleIndex, { align: 'center', behavior: 'smooth' });
}

// Scroll by relative amounts
function scrollPage(virtualizer: Virtualizer, direction: 'up' | 'down') {
  const pageSize = virtualizer.scrollRect?.height ?? 400;
  const delta = direction === 'down' ? pageSize : -pageSize;
  virtualizer.scrollBy(delta, { behavior: 'smooth' });
}

// Scroll to specific positions
function scrollToPosition(virtualizer: Virtualizer, position: number) {
  virtualizer.scrollToOffset(position, { align: 'start' });
}

Position Calculations

Methods for calculating positions and offsets:

/**
 * Get the scroll offset needed to show a specific item
 * @param index - Index of the item
 * @param align - How to align the item (default: 'auto')
 * @returns Tuple of [offset, actualAlignment] or undefined if item not found
 */
getOffsetForIndex(index: number, align?: ScrollAlignment): [number, ScrollAlignment] | undefined;

/**
 * Calculate the aligned offset for a given position
 * @param toOffset - Target offset position
 * @param align - Alignment mode
 * @param itemSize - Size of the item being aligned (optional)
 * @returns Calculated offset position
 */
getOffsetForAlignment(toOffset: number, align: ScrollAlignment, itemSize?: number): number;

/**
 * Find the virtual item at a specific scroll offset
 * @param offset - Scroll offset position
 * @returns VirtualItem at that position, or undefined if none found
 */
getVirtualItemForOffset(offset: number): VirtualItem | undefined;

Element Utilities

Utility methods for working with DOM elements:

/**
 * Extract the item index from a DOM element's data-index attribute
 * @param node - The DOM element to inspect
 * @returns The item index, or -1 if not found or invalid
 */
indexFromElement(node: TItemElement): number;

Usage Examples:

// Handle click events on virtual items
function handleItemClick(event: MouseEvent, virtualizer: Virtualizer) {
  const element = event.target as HTMLElement;
  const index = virtualizer.indexFromElement(element);
  if (index >= 0) {
    console.log(`Clicked item at index ${index}`);
  }
}

// Find item at current scroll position
function getCurrentItem(virtualizer: Virtualizer) {
  const currentOffset = virtualizer.scrollOffset ?? 0;
  const item = virtualizer.getVirtualItemForOffset(currentOffset);
  return item;
}

// Calculate positions for custom scroll animations
function animateToItem(virtualizer: Virtualizer, targetIndex: number) {
  const offsetInfo = virtualizer.getOffsetForIndex(targetIndex, 'center');
  if (offsetInfo) {
    const [targetOffset] = offsetInfo;
    // Implement custom animation to targetOffset
    animateScrollTo(targetOffset);
  }
}

Configuration and Options

The Virtualizer requires comprehensive configuration through VirtualizerOptions:

export interface VirtualizerOptions<TScrollElement extends Element | Window, TItemElement extends Element> {
  // Required configuration
  count: number;
  getScrollElement: () => TScrollElement | null;
  estimateSize: (index: number) => number;
  scrollToFn: (offset: number, options: ScrollOptions, instance: Virtualizer<TScrollElement, TItemElement>) => void;
  observeElementRect: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (rect: Rect) => void) => void | (() => void);
  observeElementOffset: (instance: Virtualizer<TScrollElement, TItemElement>, cb: (offset: number, isScrolling: boolean) => void) => void | (() => void);
  
  // Optional configuration with defaults
  debug?: boolean; // false
  initialRect?: Rect; // { width: 0, height: 0 }
  onChange?: (instance: Virtualizer<TScrollElement, TItemElement>, sync: boolean) => void;
  measureElement?: (element: TItemElement, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, TItemElement>) => number;
  overscan?: number; // 1
  horizontal?: boolean; // false
  paddingStart?: number; // 0
  paddingEnd?: number; // 0
  scrollPaddingStart?: number; // 0
  scrollPaddingEnd?: number; // 0
  initialOffset?: number | (() => number); // 0
  getItemKey?: (index: number) => Key; // (index) => index
  rangeExtractor?: (range: Range) => Array<number>; // defaultRangeExtractor
  scrollMargin?: number; // 0
  gap?: number; // 0
  indexAttribute?: string; // 'data-index'
  initialMeasurementsCache?: Array<VirtualItem>; // []
  lanes?: number; // 1
  isScrollingResetDelay?: number; // 150
  useScrollendEvent?: boolean; // false
  enabled?: boolean; // true
  isRtl?: boolean; // false
  useAnimationFrameWithResizeObserver?: boolean; // false
}

export interface ScrollOptions {
  adjustments?: number;
  behavior?: ScrollBehavior;
}

Performance Considerations

Efficient Range Calculation

The Virtualizer uses binary search algorithms to efficiently calculate visible ranges even with thousands of items.

Memory Management

  • Automatically cleans up disconnected DOM elements
  • Manages ResizeObserver connections efficiently
  • Caches measurements to avoid unnecessary calculations

Scroll Performance

  • Debounced scroll end detection
  • Optimized for both mouse wheel and touch scrolling
  • Support for native scrollend events where available

Install with Tessl CLI

npx tessl i tessl/npm-tanstack--react-virtual

docs

index.md

react-hooks.md

types-utilities.md

virtualizer-engine.md

tile.json