Virtualised list — renders only visible items for 10⁴–10⁶ entries.
Module virtualScroll | Source packages/front/fw/src/dom/rendering/virtualScroll.js | Deps dom, events | Worker-safe no
Virtualises the rendering of a long list: only items within the visible window plus a configurable overscan are materialised in the DOM. The rest is represented by a CSS spacer of calculated height, avoiding any memory cost for off-screen nodes.
Two height-calculation modes:
fixed(MVP, default): constantitemHeight→ range calculated in O(1), no DOM measurements.variable: heights measured after insertion, cached inMap<idx, height>;setTotalinvalidates the cache beyond the new index.
Resolve
const virtualScroll = runtime.resolve('virtualScroll');
// Returns: { create }
const list = virtualScroll.create({ container, itemHeight, total, renderItem });
API
| Method | Signature | Returns |
|---|---|---|
create |
(opts) => Instance |
Virtualised list instance |
refresh |
() => void |
Re-renders the visible range |
setTotal |
(n: number) => void |
Updates total + refresh |
scrollTo |
(idx: number, opts?: ScrollOpts) => void |
Programmatic scroll |
visibleRange |
() => {start: number, end: number} |
Current visible indices |
measure |
(idx: number) => number |
Measured height (variable mode) |
dispose |
() => void |
Cleans up listeners + DOM |
create options
| Option | Type | Default | Description |
|---|---|---|---|
container |
Element |
— | Scrollable element (overflow: auto). |
itemHeight |
number |
— | Item height in px. |
total |
number |
— | Total number of items. |
renderItem |
(idx: number) => HTMLElement | ElmNode[] |
— | Creates a DOM item. |
overscan |
number |
3 |
Extra items above/below. |
mode |
'fixed' | 'variable' |
'fixed' |
Height-calculation mode. |
scrollTo options
| Option | Type | Default | Description |
|---|---|---|---|
align |
'start' | 'center' | 'end' | 'auto' |
'start' |
Target alignment in the viewport. |
Examples
Simple list (fixed mode)
const virtualScroll = runtime.resolve('virtualScroll');
const list = virtualScroll.create({
container: document.getElementById('list'),
itemHeight: 32,
total: 50000,
renderItem(idx) {
const li = document.createElement('li');
li.textContent = `Item ${idx}`;
return li;
}
});
// Go to item 10000
list.scrollTo(10000);
// Update after loading additional data
list.setTotal(75000);
// Cleanup
list.dispose();
Variable mode (measured heights)
const list = virtualScroll.create({
container: document.getElementById('feed'),
itemHeight: 60, // default height before measurement
total: 1000,
renderItem(idx) {
const card = document.createElement('article');
card.className = 'card';
card.textContent = data[idx].body;
return card;
},
mode: 'variable'
});
// Measured height after rendering
console.log(list.measure(5)); // actual px of item 5
Scroll with centred alignment
list.scrollTo(500, { align: 'center' });
Notes
- In
fixedmode, range calculation is O(1) — optimal for homogeneous long lists (TTY scrollback, task manager). - In
variablemode,getBoundingClientRect()is called after insertion into the layer; happy-dom does not return real layout, so heights stay at the default in tests. renderItemmay return anHTMLElementor an elm-array (array of elm nodes) — the bridge usesdom.createif available, with fallback to the first node of the array.disposeremoves the spacer and the layer from the container and removes the scroll listener; a second call is a no-op.- The scroll listener is registered in
passivemode to avoid blocking the main thread.