一万行の一覧

一万件の受注を表示する画面です。DOM に入っているのは、見えている二十数行だけです。

スクロール位置から描く行を決める

一覧の枠のスクロール位置はイベントで届きます。仮想スクロールは言語の機能ではなく、画面の書き方です。

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

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

行の高さは CSS で決めた定数です。スクロール位置を行の高さで割ると、最初に描く行の番号が出ます。

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

前後に二行の余裕を持たせているのは、速くスクロールしたときに端が欠けないようにするためです。

スペーサー

描かない行の分の高さは、上下のスペーサーの行に持たせます。これでスクロールバーの長さが一万行分になります。

高さ 0 のスペーサーは出力しません。tbody tr に高さを指定している表では、高さ 0 の tr も一行分の高さになるためです。

描画時間

一描画
1,000 行を全部描く20.8 ms
10,000 行を仮想スクロール4.9 ms

仮想スクロールでは、一覧の行数に関わらず描画時間は一定です。

テスト

スペーサーの高さ、sticky な見出し行の高さ、端で一部だけ見えている行の扱いは、ブラウザのレイアウトで決まります。DOM を模したテスト環境では確認できないので、本物のブラウザをヘッドレスで動かし、スクロール後にその位置に描かれている行をブラウザに問い合わせてテストしています。行の高さを 2px ずらすと、SO-10179 の位置に SO-10167 が来てテストが失敗します。