Reactive Virtualizer
Problem
You are building a component with @vielzeug/ripple or @vielzeug/craft and want scroll state to flow through the reactive graph — no manual onChange wiring.
Solution
Use createReactiveVirtualizer. It wraps createVirtualizer and exposes state as a Signal<VirtualizerState>. Use effect to re-render whenever the visible window changes.
ts
import { createReactiveVirtualizer } from '@vielzeug/scroll';
import { effect } from '@vielzeug/ripple';
const rows = Array.from({ length: 50_000 }, (_, i) => ({ id: i, label: `Row ${i}` }));
const scrollEl = document.getElementById('scroll')!;
const listEl = document.getElementById('list')!;
const virt = createReactiveVirtualizer(scrollEl, {
count: rows.length,
estimateSize: 36,
});
// Re-render whenever the visible window changes
effect(() => {
const { items, totalSize } = virt.state.value;
listEl.style.height = `${totalSize}px`;
listEl.innerHTML = '';
for (const item of items) {
const el = document.createElement('div');
el.style.cssText = `position:absolute;top:${item.start}px;left:0;right:0;height:36px;line-height:36px;padding:0 12px;`;
el.textContent = rows[item.index].label;
listEl.appendChild(el);
}
});
// Standard virtualizer methods are available directly
virt.scrollToIndex(rows.length - 1, { align: 'end', behavior: 'smooth' });
virt.update({ count: rows.length });
// Cleanup
virt.dispose();Reading state outside an effect
virt.state is a standard Signal<VirtualizerState>. Read .value anywhere:
ts
const { items, totalSize } = virt.state.value;
console.log(`${items.length} items visible, total ${totalSize}px`);Combining with a computed signal
ts
import { computed } from '@vielzeug/ripple';
const visibleCount = computed(() => virt.state.value.items.length);
effect(() => {
statusEl.textContent = `Showing ${visibleCount.value} of ${virt.count} rows`;
});Pitfalls
onChangemust not be passed tocreateReactiveVirtualizer— it is wired internally to updatevirt.state.- The
statesignal updates synchronously within the scroll handler. Avoid heavy DOM operations directly insideeffect— batch DOM writes withrequestAnimationFrameif needed. - All live getters (
count,items,totalSize,scrollOffset,stickyItems) remain current on the returned virtualizer. The implementation uses aProxyrather than snapshotting.