CSSの position: sticky
を使ってテーブルのヘッダー行・列を固定する方法を解説します。動作確認したブラウザーは次のとおりです。
- Google Chrome 71
- Firefox 64
- Safari 12
- Microsoft Edge 43 (EdgeHTML 17)
ちなみに、IE 11などの対応していないブラウザーで見た場合、ヘッダー行・列が固定されないだけで、表示が崩れたりはしません。
theadを固定するかthを固定するか
縦スクロール時にヘッダー行を固定するにあたり、thead
に対して position: sticky
を指定したくなりますが、これはあまり筋が良くなさそうです。まずChromeやEdgeは thead
や tr
の固定に対応していません1。 display: block
などで回避できますが、列の幅が決まっていないといけないという制約が生まれたり、本質的でないスタイルの記述が増えたりします2。
一方 th
を固定すると、記述がシンプルになり、列の幅も予め決めなくて済みます。このため、ChromeやEdgeが thead
の固定に対応していない現状では、thead
を固定するのではなく、 thead
内の th
を固定するのが無難です3。 ただし複数のヘッダー行を固定したい場合はちょっと工夫が必要なので後述します。
ヘッダーをビューポートの上と左に固定する
まずはビューポート(ウィンドウ)の上と左に固定します。
デモ: StickyTable (thをビューポートに固定)
以下のような記述で簡単に固定できます。デザインのためのCSSは省略しています。
HTML
1行目と1列目がヘッダーというよくあるテーブルです。
<table class="sticky_table">
<thead>
<tr>
<th></th>
<th>head</th>
<th>head</th>
<!-- ... -->
</tr>
</thead>
<tbody>
<tr>
<th>left</th>
<td>data</td>
<td>data</td>
<!-- ... -->
</tr>
<!-- ... -->
</tbody>
</table>
CSS
縦スクロール時にヘッダー行を固定します。
.sticky_table thead th {
/* 縦スクロール時に固定する */
position: -webkit-sticky;
position: sticky;
top: 0;
/* tbody内のセルより手前に表示する */
z-index: 1;
}
横スクロール時にヘッダー列を固定します。
.sticky_table th:first-child {
/* 横スクロール時に固定する */
position: -webkit-sticky;
position: sticky;
left: 0;
}
横スクロール時に左上隅のセルが他のセルより手前に来るようにします。
.sticky_table thead th:first-child {
/* ヘッダー行内の他のセルより手前に表示する */
z-index: 2;
}
バリエーション1: ヘッダーをoverflow: scrollな要素の上と左に固定する
position: sticky
を指定した要素は、一番近いscrolling ancestorに固定されます。scrolling ancestorとは、祖先要素のうち overflow: hidden, scroll, auto, overlay
な要素です。そのような要素が無い場合はビューポートと考えて良いでしょう。
先ほどのテーブルを overflow: scroll
を指定した要素で囲めば、その要素内でスクロールしたときにヘッダーが上と左に固定されます。
デモ: StickyTable (thをoverflow: scrollな要素に固定)
HTML
テーブルをスクロールする要素で囲います。
<div class="sticky_table_wrapper">
<table class="sticky_table">
<!-- 上記と同様 -->
</table>
</div>
CSS
上記のCSSに加えて、次のCSSを書きます。 width
や height
は好みで設定して下さい。
.sticky_table_wrapper {
overflow: scroll;
width: calc(100vw - 1rem);
height: 75vh;
}
バリエーション2: 複数のヘッダー行を固定したい場合
th
を固定する方針では、複数のヘッダー行があるときに工夫が必要です。ヘッダー行の高さを予め決めるという制約を導入すれば、各ヘッダー行の top
の値を変えてやることで意図した場所に固定できます。
デモ: StickyTable (ヘッダー行が複数の場合にthをビューポートに固定)
例えば thead
内のヘッダー行が3行ある場合、縦スクロール時に固定するにはスタイルを次のように変更します。
.sticky_table thead th {
/* 縦スクロール時に固定する */
position: -webkit-sticky;
position: sticky;
/* 高さが変わらないよう改行させない */
white-space: nowrap;
}
.sticky_table thead tr:nth-child(1) th {
top: 0;
}
.sticky_table thead tr:nth-child(2) th {
top: 1.5rem; /* 2行目は1行目の高さの位置に固定する */
}
.sticky_table thead tr:nth-child(3) th {
top: 3rem; /* 3行目は1〜2行目の高さの位置に固定する */
}
表のボーダーについて
この記事では深入りしませんが、表にボーダーがあると固定されたときに微妙にセルの位置がずれて見えたり、一部のボーダーがレンダリングされないことがあります。気になる場合は、次の2つの回避策のいずれかが良いかと思います。
- 表にボーダーを使用しない
-
border-collapse: separate
を使う
参考
- position | MDN
- Can I use... Support tables for HTML5, CSS3, etc
- CSSのみで行・列ヘッダ固定テーブルを実装(Firefox / Chrome / Safari) - Qiita (※theadを固定する方針。CodePenをForkさせていただきました。感謝!)
- CSSスニペット:position: sticky;でtableを下スクロール時はヘッダ、横スクロール時は最左を固定 | かちびと.net
- Firefox/Chromeでtable要素の「position: sticky」が動作しない問題の対処: 小粋空間 (※theadを固定する方針)
- position: stickyの面白い使い方と使用時の注意点 | Rriver
- table に position: sticky を使ってみた | WEBMAN
- CSSのposition:stickyがすごく便利 | q-Az
- [CSS] z-index とスタックコンテキスト - Qiita
-
Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=702927 Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16765952/ ↩
-
参考: https://qiita.com/s0tter/items/14fb4ec2600828a21a22 個人的に別のアプローチを試したところ、SafariやEdgeで左上隅のセルが正しくレンダリングされませんでした(しかもSafariの場合
iframe
内では発生しないみたいでわかりづらい)。 https://codepen.io/orangain/pen/zyMzga ↩ -
将来的にChromeやEdgeでも
thead
を固定できるようになればそのほうが簡単かもしれませんが、Issueを見る限り時間がかかりそうです。 ↩