LoginSignup
243
230

More than 5 years have passed since last update.

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

Posted at

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を見る限り時間がかかりそうです。 

243
230
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
243
230