Ten thousand rows

A list of ten thousand orders. Only the visible rows, about two dozen, are in the DOM.

Deciding which rows to draw

The scroll position of the list container arrives as an event. Virtual scrolling is a way of writing the screen, not a language feature.

me listen: '#box' on: #scroll sendEvent: #scrolled:.

Screen >> scrolled: e = self with: { top: e scrollTop. viewH: e height }.

The row height is a constant fixed by the CSS. Dividing the scroll position by the row height gives the first row to draw.

Screen >> firstDrawn = ((top // Screen rowHeight) - 2) max: 0.

Two extra rows on either side keep the edges from showing gaps during fast scrolling.

Spacers

The height of the rows that are not drawn is given to spacer rows above and below the drawn rows. This keeps the scrollbar the length of ten thousand rows.

A spacer of height 0 is not emitted. In a table that sets a height on tbody tr, a tr of height 0 would still take up one row.

Render time

One render
1,000 rows, all drawn20.8 ms
10,000 rows, virtual scrolling4.9 ms

With virtual scrolling, render time does not depend on the number of rows in the list.

Testing

The spacer heights, the height of the sticky header row, and the handling of partially visible rows at the edges are determined by the browser's layout. They cannot be tested in a simulated DOM, so the test runs a real headless browser, scrolls, and asks the browser which row is drawn at a given position. Changing the row height by 2px puts SO-10167 where SO-10179 should be, and the test fails.