データ量の多い <table> を扱うとき、<thead>部分だけを固定して <tbody> の部分だけ縦スクロールしたい。
という場面があるかと思います。
今回は、Table のヘッダ固定について、採用したパターンのメモです。
Table の固定にあたって、検索で調べると以下のような手段があるようです。
- HTMLだけでなんとかする。(ヘッダを別のtableに分ける…とかそんな感じのもの)
- CSSだけでなんとかする。(ヘッダ部分をpositionで固定するとか、tbodyにoverflowかけたりする)
- JavaScriptを使ってなんとかする。(SuperTable とか使って)
- CSSとJavaScriptを使ってなんとかする。(2 と 3 の合わせ技?)
正しい<table>ダグを維持して、内容(tbody部分)が縦スクロールするのが理想。
ということで、まずはCSSでなんとかするという方法を取ってみることに。
対応させるブラウザはIE6〜IE8、FireFox(Win/Mac)。
まず、こんな<table>を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <div class="scrollTableBox"> <table class="scrollTable"> <thead> <tr> <th>ヘッダタイトル1</th> <th>ヘッダタイトル2</th> <th>余分なセル</th> <tr> </thead> <tbody> <tr> <td>コンテンツデータ1</td> <td>コンテンツデータ2</td> <td>余分なセル</td> </tr> <tr> <td>コンテンツデータ3</td> <td>コンテンツデータ4</td> <td>余分なセル</td> </tr> </tbody> </table> </div> |
ここで、スクロールさせたい箇所( <tbody> )に対して、CSSで以下のようにスタイルを指定します。
1 2 3 4 5 6 7 | <style type="text/css"> .scrollTable tbody { overflow: auto; overflow-x: hidden; height: 任意の高さpx; } </style> |
この時点で、FireFoxはきちんと <tbody> に対して overflow:auto; が正しく適用されています。
<table>の右端に余分な列を加えているのは、<tbody> の右端にスクロールバーが入ることで、最後の列だけがスクロールバー分ほど大きくなってしまうため、これを避けるために余分な列を用意しているのです。
(<table>の列の見た目を絶対に変えたりしたくないときに、回避法として用意するといいです)
FireFoxだけなら上記の設定で充分なのですが、IE6〜IE8までの対応もしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!--[if lt IE 8]> <style type="text/css"> div.scrollTableBox { overflow: auto; height: 400px; } div.scrollTableBox table tbody { overflow: visible; height: auto; } div.scrollTableBox table thead tr { position: relative; top: expression(offsetParent.scrollTop-window.scrollTop); } div.scrollTableBox table thead tr th { position: relative; top: expression(offsetParent.scrollTop-window.scrollTop); } </style> <![endif]--> |
IE対応の為にやむなくcssの中で expression を使用しています。
こちらは <table> の外側にある <div> に対して overflow:auto; を指定しています。
このままでは、<div> の中にある <table> そのものがスクロールしてしまいます。
そこで、固定したいヘッダ部分( <thead> )を position と expression を使用して、<div> がスクロールされたとき、スクロールしている分の高さの値を top に設定して、<div> をスクロールしても固定されるようにしています。
これで、IE6とIE7は <tbody> のみスクロールが効くようになります。
※ちなみに、cssに expression を使用するときは外部CSSファイルではなく、<head> 内に書く必要があります。
【2009.11.01加筆】
1 | top: expression(offsetParent.scrollTop-window.scrollTop); |
について補足します。
1 | expression |
に記述されている
1 | -window.scrollTop |
は、table が ブラウザより大きくなった場合の対処になります。
table をブラウザの高さに充分収まるように作成しても、ユーザーの環境によってブラウザが自分の意図しないサイズ(tableよりも小さい)だったという場合もあります。
上記の場合、
1 | top: expression(offsetParent.scrollTop); |
だと以下のようになります。

top: expression(offsetParent.scrollTop); の場合
ウィンドウのスクロールバーを動かすと、 tbody 自体はスクロールされているのですが、 thead だけは固定されたままになっています。
1 | top: expression(offsetParent.scrollTop-window.scrollTop); |
の場合は、以下のようになります。

top: expression(offsetParent.scrollTop-window.scrollTop); の場合
ウィンドウのスクロールバーを動かすと、thead は固定されることなく、 table 全体がスクロールされています。
Window幅も固定してしまったうえでスクロールTableを作成するのであれば、
1 | -window.scrollTop |
の記述は必要ありません。
(2009.11.01加筆 ここまで)
IE8は、expression についてはサポート外となってしまっているので、IE8 対応のために以下のMETAタグを挿入します。
1 | <meta http-equiv="X-UA-Compatible" content="IE=emulateIE7"> |
これで、下位互換(IE7スタンダードモード)になり、expression の指定がIE8でも有効になります。
以上で完了。
はじめ、理想の手段としてCSSだけでなんとかするという方向に向かってましたが、IE対応のために expression を使ったので、最終的にはCSSとJavaScroptでなんとかするということに…。
ちなみに、以上の設定でスクロールが動作しなかったブラウザは、Safari(Mac/Win)、Opera(Win)、Chrome(Win)でした。
逆に、古いFireFox(1.5)は、きちんとスクロールされました。
固定させる Table は、レイアウトが単純なほど対応させやすいと思います。
スクロールさせたい <table> の <th> に colspan や、 rowspan があるとIE側ではヘッダを固定したときに <table> のセルの幅が崩れたりすることがほとんどです。
その時は、<thead> の構造をなるべく複雑にならないようにする工夫も必要になってきます。
【2009.09.30加筆】
Tableスクロールを実装したときのキャプチャ画像です。

スクロールするテーブル(キャプチャ画像)
このテーブルはわりとヘッダ部分が複雑になっていますが、崩れることなく tbody部分だけがスクロールされます。
css / html ともにコードは以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <style type="text/css"> div.scroll_table table tbody { overflow: auto; height: 130px; overflow-x: hidden; } </style> <!--[if lt IE 8]> <style type="text/css"> div.scroll_table { overflow: auto; height: 400px; } div.scroll_table table tbody { overflow: visible; height: auto; } div.scroll_table table thead tr { position: relative; top: expression(offsetParent.scrollTop-window.scrollTop); } div.scroll_table table thead tr th { position: relative; top: expression(offsetParent.scrollTop-window.scrollTop); } </style> <![endif]--> <style type="text/css"> th { background: #ccc; } th, td { border-left:1px #666 solid; border-bottom:1px #666 solid; } th.th_top { border-top:1px #666 solid; } .dummy { background:none; border-bottom:none; } </style> |
※CSSの注意点としては、 table に border-collapse:collapse; を指定しないこと。指定すると、テーブルが崩れてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | <div class="scroll_table"> <table cellspacing="0"> <colgroup> <col style="width:20px"> <col style="width:30px;"> <col style="width:50px;"> <col style="width:50px;"> </colgroup> <colgroup span="24" style="width:10px;"></colgroup> <colgroup><col style="width:20px;"></colgroup> <thead> <tr> <th rowspan="4" class="th_top"> </th> <th class="th_top"> </th> <th colspan="2" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th colspan="2" rowspan="4" class="th_top"> </th> <th class="dummy" rowspan="4"> </th> </tr> <tr> <th> </th> <th colspan="2"> </th> </tr> <tr> <th> </th> <th> </th> <th> </th> </tr> <tr> <th colspan="3"> </th> </tr> </thead> <tbody> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> <tr> <td rowspan="3"> </td> <td> </td> <td colspan="2"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3"> </td> <td rowspan="3" class="dummy"> </td> </tr> <tr> <td> </td> <td colspan="2"> </td> </tr> <tr> <td> </td> <td> </td> <td> </td> </tr> </tbody> </table> </div> |
以上のコードをそのまま使えば、上のキャプチャ画像のようなスクロールするテーブルが再現できると思います。
※ただし、 rowspan が 複雑になった場合、IE6などではテーブルが崩れてしまいます。
(FireFoxでは崩れることはありません…)

th の rowspan が複雑だと崩れます
IE6対応が前提であれば、複雑な rowspan は避けた方が良いと思われます。
もっとスマートにテーブルスクロールできたらなぁ…。
【2009.10.06 加筆】
上記のソースコードで Table スクロールをIE8に実装したとき、Table の内容が ウィンドウ幅を超えると thead が横スクロールされません。

thead がウィンドウ幅を超えたスクロールテーブル
thead の部分が固定された状態で、tbody部分だけがスクロールされてしまいます。
これは、 thead を
1 | position:relative |
で固定しており、さらに IEの時は table 外側にある <div> を
1 | overflow:auto |
にすることでスクロールを実現していることが原因になります。

IE のときのスクロールテーブルの動作
サンプル用のソースコードでは、 <table> や <div> に対して width の設定は特にしていませんでした。
そのため、 <thead> の内容が <td> の幅を超えて、<table> 全体がウィンドウ幅より大きくなってしまったとき、IEでは <thead> が横スクロールされずに上記キャプチャ画像のようなおかしな表示になってしまいます。
これを回避するには、
- <table> の width を指定する
- <table> の width + 20px した幅を <div> に指定する
この2点を行うことが必要です。
<div> の幅が<table> より若干大きくなっているのは、同じ幅に指定すると、ほんの少しだけ横スクロールが表示されてしまうからです。
そのため、<div> には 20px くらい幅を持たせてあげます。
ちなみに、今回のサンプルはあくまでも 縦スクロール対応 が目的だったので、横スクロールのことは考えてなかったりします…。ごめんなさい。
素晴らしいできばえです。
expressionを使用する事で、複数行タイトルへ簡単に対応できるんですね。
これをIE8で試して見て、
タイトルに長い文字を入れて、あえてwindow幅からはみ出すようにすると横スクロールでタイトルが固定されてしまいました。
との横幅が同じならないようです。
解決策が分からなかったで、の横幅を過大にすると解決するようなのですが..
なにか良い方法はありませんか?