Ten thousand rows
Ten thousand orders in a list. What is actually in the DOM is the two dozen rows you can see.
The language hands over one fact
Windowing is not a feature of the language. What the language provides is where the region is scrolled to.
me listen: '#box' on: #scroll sendEvent: #scrolled:.
Screen >> scrolled: e = self with: { top: e scrollTop. viewH: e height }.
The row height is a number the CSS already decided, so the program knows it rather than measuring. Divide, and you have which rows are needed.
Screen >> firstDrawn = ((top // Screen rowHeight) - 2) max: 0.
Two rows of slack at each end keep the edges filled when you scroll fast.
Padding keeps the scrollbar honest
The height of what is not drawn is carried by one padding row above and one below. They hold height and nothing else, so the scrollbar is as long as ten thousand rows.
A zero-height padding row is left out entirely. In a table that gives tbody tr a height, a tr of height zero does not vanish — it stays as one row of blank.
Speed
One render costs the same however long the list is, because what is drawn is always what is in view.
| one render | |
|---|---|
| 1,000 rows, all drawn | 20.8 ms |
| 10,000 rows, windowed | 4.9 ms |
Only a browser can check this
The padding height, the share of the frame a sticky header takes, a row half-visible at an edge — layout decides all of it, so a test against a shim has nothing to say.
So a real browser is driven headless: scroll, then ask the browser what is actually drawn at a point. Put the row height two pixels out and SO-10167 turns up where SO-10179 belongs, and the check fails.