Variable Height with Measurement
Problem
Row heights vary based on content (e.g., multi-line text, embedded images). The virtualizer cannot compute offsets upfront — it must accept measured heights reported after each row renders.
Solution
Report exact heights after rendering using measure(). All measurement calls within a single tick are coalesced into one rebuild.
ts
import { createVirtualizer } from '@vielzeug/virtualit';
type Message = { id: number; content: string };
const messages: Message[] = [
/* ... */
];
const virt = createVirtualizer(scrollEl, {
count: messages.length,
estimateSize: 64, // rough estimate — actual heights vary
onChange: (virtualItems, totalSize) => {
listEl.style.height = `${totalSize}px`;
listEl.innerHTML = '';
for (const item of virtualItems) {
const el = document.createElement('div');
el.dataset.index = String(item.index);
el.className = 'message';
el.style.cssText = `position:absolute;top:${item.start}px;left:0;right:0;padding:8px;`;
el.textContent = messages[item.index].content;
listEl.appendChild(el);
}
// Measure after layout — batched into one rebuild
requestAnimationFrame(() => {
for (const item of virtualItems) {
const el = listEl.querySelector<HTMLElement>(`[data-index="${item.index}"]`);
if (el) virt.measure(item.index, el.offsetHeight);
}
});
},
});Pitfalls
measure(index, height)must be called after the row's DOM is rendered and its height is stable. Calling it before render with an estimate triggers two layout passes per row.- Call
measure()inonMounted/firstUpdatedor aResizeObserver— not in a scroll event handler, where it causes a measurement/layout loop. - If a row's height changes after initial measurement (e.g., a "show more" expansion), call
measure()again. The virtualizer does not observe DOM height changes automatically.