Posted at

CSSのposition: stickyでテーブルのヘッダー行・列を固定する

CSSの position: sticky を使ってテーブルのヘッダー行・列を固定する方法を解説します。動作確認したブラウザーは次のとおりです。


  • Google Chrome 71

  • Firefox 64

  • Safari 12

  • Microsoft Edge 43 (EdgeHTML 17)

ちなみに、IE 11などの対応していないブラウザーで見た場合、ヘッダー行・列が固定されないだけで、表示が崩れたりはしません。


theadを固定するかthを固定するか

縦スクロール時にヘッダー行を固定するにあたり、thead に対して position: sticky を指定したくなりますが、これはあまり筋が良くなさそうです。まずChromeやEdgeは theadtr の固定に対応していません1display: 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を書きます。 widthheight は好みで設定して下さい。

.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 を使う


参考





  1. Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=702927 Edge: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16765952/ 



  2. 参考: https://qiita.com/s0tter/items/14fb4ec2600828a21a22 個人的に別のアプローチを試したところ、SafariやEdgeで左上隅のセルが正しくレンダリングされませんでした(しかもSafariの場合 iframe 内では発生しないみたいでわかりづらい)。 https://codepen.io/orangain/pen/zyMzga 



  3. 将来的にChromeやEdgeでも thead を固定できるようになればそのほうが簡単かもしれませんが、Issueを見る限り時間がかかりそうです。