| Crates.io | virtualizer |
| lib.rs | virtualizer |
| version | 0.4.0 |
| created_at | 2025-12-30 06:06:23.629663+00 |
| updated_at | 2026-01-13 15:12:52.064679+00 |
| description | A headless virtualization engine inspired by TanStack Virtual. |
| homepage | https://github.com/Latias94/virtualizer |
| repository | https://github.com/Latias94/virtualizer |
| max_upload_size | |
| id | 2012155 |
| size | 158,444 |
A headless virtualization engine inspired by TanStack Virtual.
This crate provides the core algorithms needed to virtualize massive lists at interactive frame rates:
measure / resize_item.scroll_to_index_offset, Align).It is UI-agnostic and can be used in TUIs/GUI frameworks.
use virtualizer::{Align, Virtualizer, VirtualizerOptions};
let mut v = Virtualizer::new(VirtualizerOptions::new(1_000_000, |_| 1));
v.set_viewport_size(20);
v.set_scroll_offset(100);
let mut items = Vec::new();
v.for_each_virtual_item(|it| items.push(it));
assert!(!items.is_empty());
let off = v.scroll_to_index_offset(1234, Align::Start);
v.set_scroll_offset(off);
If you want clamped scroll offsets (useful for scroll-to helpers), use set_scroll_offset_clamped
or clamp_scroll_offset.
If your UI layer wants keys alongside items, use for_each_virtual_item_keyed.
If you want to scroll to a raw offset (TanStack scrollToOffset), set it via
set_scroll_offset_clamped (or set_scroll_offset if you want to handle clamping yourself).
For an adapter-style walkthrough (rect/scroll events/dynamic measurement), see
examples/adapter_sim.rs (cargo run -p virtualizer --example adapter_sim).
For more end-to-end adapter scenarios (tween scrolling, pinned headers, keyed reorders, dynamic
measurement), see examples/README.md.
If you want adapter-level utilities (scroll anchoring for prepend, tween helpers), see the optional
virtualizer-adapter crate in this workspace (../virtualizer-adapter/).
If you call for_each_virtual_item every frame, consider reusing a Vec to avoid per-frame
allocations:
use virtualizer::{Virtualizer, VirtualizerOptions};
let v = Virtualizer::new(VirtualizerOptions::new(10_000, |_| 1));
let mut items = Vec::new();
items.clear();
v.for_each_virtual_item(|it| items.push(it));
Similar APIs exist for keys and indexes: for_each_virtual_item_keyed and for_each_virtual_index.
If you need pinned/sticky rows (e.g. headers), set range_extractor to emit the exact set of
indexes you want to render. The extractor is zero-allocation: it receives an emit callback.
Use IndexEmitter to make this ergonomic and correct (sorted + deduped):
use std::sync::Arc;
use virtualizer::{IndexEmitter, Range, VirtualizerOptions};
let mut opts = VirtualizerOptions::new(1000, |_| 1);
opts.range_extractor = Some(Arc::new(|r: Range, emit: &mut dyn FnMut(usize)| {
let mut e = IndexEmitter::new(r, emit);
e.emit_pinned(0);
e.emit_overscanned();
}));
Use VirtualizerOptions::new_with_key if you want a non-u64 key type (e.g. for stable caching
across reorders in your UI layer).
use virtualizer::{Virtualizer, VirtualizerOptions};
let opts = VirtualizerOptions::new_with_key(100, |_| 1, |i| format!("row-{i}"));
let v: Virtualizer<String> = Virtualizer::new(opts);
assert_eq!(v.key_for(7), "row-7");
Note: the key type must implement Hash + Eq with the default std feature, and Ord when built
with --no-default-features (no_std + alloc).
If your data is reordered/changed while count stays the same, call sync_item_keys so measured
sizes follow item keys. If you update get_item_key itself via set_get_item_key, the virtualizer
automatically rebuilds per-index sizes from the key-based cache.
use virtualizer::{Virtualizer, VirtualizerOptions};
let mut v = Virtualizer::new(VirtualizerOptions::new(2, |_| 1));
v.measure(0, 10);
v.set_get_item_key(|i| if i == 0 { 1 } else { 0 }); // reorder
assert_eq!(v.item_size(1), Some(10));
If your scroll offset is measured from a larger scroll container (e.g. window scrolling) and the
list starts after some header/content, set scroll_margin so virtual item start values match the
scroll container coordinate system:
use virtualizer::{Virtualizer, VirtualizerOptions};
let mut opts = VirtualizerOptions::new(10_000, |_| 35);
opts.scroll_margin = 50; // list begins 50px below scroll origin
let mut v = Virtualizer::new(opts);
v.set_viewport_and_scroll(500, 0);
TanStack Virtual exposes scrollRect and an initialRect option. This crate provides a platform-
agnostic Rect { main, cross }:
main: the virtualized axis size (e.g. height for vertical lists)cross: the cross axis size (e.g. width for vertical lists)Use VirtualizerOptions::with_initial_rect(Some(rect)) for an initial value (SSR-like setups), and
call set_scroll_rect(rect) from your adapter to keep it up-to-date.
TanStack Virtual allows initialOffset to be a number or a function. This crate uses
InitialOffset:
InitialOffset::Value(123)InitialOffset::Provider(|| 123)measure updates sizes and may adjust scroll position to prevent visual "jumps" when items before
the viewport change size (TanStack-aligned behavior).
If you want to update measurements without changing scroll position, use measure_unadjusted.
resize_item returns the applied scroll adjustment (delta) and also updates the virtualizer's
internal scroll_offset when adjustment is needed.
To reset all measurements (TanStack measure()), call reset_measurements.
To persist and restore measurements (key → size), use export_measurement_cache /
import_measurement_cache (see examples/measurement_cache.rs).
If you want TanStack-style change notifications, set on_change (or call set_on_change) and drive
is_scrolling via set_is_scrolling from your framework adapter.
For TanStack parity, you can also use notify_scroll_event(now_ms) + update_scrolling(now_ms) to
implement isScrollingResetDelay behavior without requiring timers in the core.
If your adapter updates multiple pieces of state in a single frame (common in UI event handlers),
use batch_update to coalesce them into a single on_change callback:
v.batch_update(|v| {
v.set_scroll_rect(rect);
v.set_scroll_offset_clamped(offset);
v.notify_scroll_event(now_ms);
});
For even more ergonomic adapter code, you can use the higher-level "event" entry points which internally batch and mark scrolling:
apply_scroll_offset_event / apply_scroll_offset_event_clampedapply_scroll_frame / apply_scroll_frame_clampedDisable default features to build in no_std environments (requires alloc):
cargo build --no-default-features
Dual-licensed under MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.