リストでスクロールペイン内に見えている行だけDOM要素を作るサンプル

[SlickGrid](https://github.com/mleibman/SlickGrid/wiki)やiOSの[UITableViewクラス](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html)は高速化のために、全ての行についてUI要素を作らずに、スクロールペイン内に見えている行だけUI要素を作ってそれを再利用するという手法を用いています。

この仕組みを理解するために、縦方向のみスクロールするシンプルなリストのサンプルを作ってみました。
ただし、このサンプルの実装はSlickGridの実装と同じではありません。

デモ: http://hnakamur.github.io/jquery-examples/virtual-scroll-list.html

マウスホイールでの操作に加えて、PageUp, PageDownキーでのページ移動やHome, Endキーで先頭、最後に移動しても正しい内容が表示されます。

ソース: https://github.com/hnakamur/jquery-examples/blob/76b1a46c7c432f445616822dc8d3e7a2476d4f21/virtual-scroll-list.html

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>web ui examples</title>
<style>
#pane {
  width: 200px;
  height: 300px;
  overflow: auto;
  background-color: #eee;
  position: relative; /* needed for IE7 */
}
#content {
  position: relative;
}
.row {
  position: absolute;
  width: 200px;
  border-width: 1px 0 1px 0;
  border-style: solid;
}
</style>
</head>
<body>
<div id="pane">
<div id="content">
</div>
</div>
<script src="js/jquery-1.11.0.js"></script>
<script>
$(function() {
  var dataCount = 10000;

  function measureRowHeight() {
    var row = $('<div class="row"/>').appendTo("#content");
    adjustRow(row, 0);
    var rowHeight = row.outerHeight();
    row.remove();
    return rowHeight;
  }

  function buildRowContent(div, i) {
    div.text(i + 1);
  }

  function adjustRow(div, i) {
    div.css('top', i * rowHeight);
    div.data("row", i);
    buildRowContent(div, i);
  }

  var rowHeight = measureRowHeight();
  $("#content").css("height", rowHeight * dataCount);

  var pane = $("#pane");
  var paneHeight = pane.outerHeight();
  var rowCount = Math.ceil(paneHeight / rowHeight) + 1;
  var rows = {};
  var hiddenRows = [];

  function initRows() {
    for (var i = 0; i < rowCount; i++) {
      var row = $('<div class="row"/>').appendTo("#content");
      adjustRow(row, i);
      rows[i] = row;
    }
  }

  function collectHiddenRows(rows, startIndex, endIndex) {
    for (var i in rows) {
      if (!(startIndex <= i && i < endIndex)) {
        hiddenRows.push(rows[i]);
        delete rows[i];
      }
    }
  }

  initRows();
  pane.scroll(function(e) {
    var scrollTop = $(this).scrollTop(),
      startIndex = Math.floor(scrollTop / rowHeight),
      endIndex = Math.min(Math.floor((scrollTop + paneHeight) / rowHeight) + 1, dataCount),
      i, row;
    collectHiddenRows(rows, startIndex, endIndex);
    for (i = startIndex; i < endIndex; i++) {
      if (!rows[i]) {
        row = hiddenRows.shift();
        adjustRow(row, i);
        rows[i] = row;
      }
    }
  });
});
</script>
</body>
</html>
```

このサンプルでは全ての行の高さが同じであることを前提としています。

一旦行を作って高さを測った後、スクロールペインの高さを行の高さで割って、ペイン内に見える行数を計算し、その数だけ行のdiv要素を作っています。

スクロールイベントハンドラでは以下の様な処理をしています。

1. スクロールペインの外にある行を一旦キュー(配列)に集める。
2. その後スクロールペイン内で見える行の範囲について、既に存在する行はそのままにして、存在しない行はキューから行を取り出して位置を移動して中身を更新する。

これにより1行ずつスクロールした時は1つの行を移動するだけですみます。

行のdivはcssで```position: absolute```にしているので、HTMLのリフローの影響も最小限ですみますし、div自体は作り直す必要がなく中身だけを更新すれば良いようになっています。

ただ、PageUp, PageDownでページ単位で切り替えた時に、OS XのChromeとIE7ではページ移動しても素早く表示されるのですが、IE8とIE9では描画中の状態が見えてしまいます。もっと良い方法があればぜひ教えてください。