CSSのみで行・列ヘッダ固定テーブルを実装（Firefox / Chrome / Safari）

## Introduction

モダンブラウザにおいて`position: sticky`と`width: max-content`の対応が進んだことで、CSSのみで行・列ヘッダ固定テーブルを容易に実装できるようになりました。

なお、各ブラウザの対応状況が異なるため、現状ではセルの横幅（場合によっては高さも）を明示する課題を残してはいますが、保守性は大幅に向上されました。


## Goal

![20170907-225500_capture.gif](https://qiita-image-store.s3.amazonaws.com/0/140939/03143f58-46f6-1f51-b86b-a95be0d1b406.gif)

[StickyTable | CodePen](https://codepen.io/s0tter/pen/ayQOJR)

## Support

本実装での各ブラウザの対応状況です。

| ブラウザ | バージョン | 対応状況 |
|:--|:--|:--:|
| Chrome | v.60 | 〇 |
| Firefox | v.55 | 〇 |
| Safari | v.10.1 | 〇 |
| Edge | v.15 | × |


## How to

### HTML

```html
<table class="sticky_table">
  <thead>
    <tr>
      <th class="blank">&nbsp;</th>
      <th>head</th>
      <th>head</th>
      <!-- ... -->
    </tr>
    <!-- ... -->
  </thead>
  <tbody>
    <tr>
      <th>left</th>
      <td>data</td>
      <td>data</td>
      <!-- ... -->
    </tr>
    <!-- ... -->
  </tbody>
</table>
```

HTMLはシンプルなテーブルの記述です。
ヘッダ固定テーブルを実装するために以下を行います。

- tableタグに`sticky_table`クラスを付与する。
- theadのtrの1列目（固定列と固定行の交差部分）に`blank`クラスを付与する。


### CSS

#### tableタグ

```css
.sticky_table {
  display: block;
  position: relative;
  overflow: scroll;
  width: calc(100vw - 3.0rem);
  height: calc(75vh);
  border-collapse: collapse;
  font-size: 0;
}
```

tableタグは幅と高さを決め、子要素がはみ出した際にスクロールするようにします。

`position: relative`は、子要素の位置が相対指定となった際の基準として明記します。

`font-size: 0`は後述のinline-block要素を横並びにした際に生じる隙間を回避するために指定します。


#### thead, tbodyタグ

```css
.sticky_table thead,
.sticky_table tbody {
  display: block;
  width: -webkit-max-content;
  width: -moz-max-content;
  width: max-content;
}
```

theadとtbodyタグは幅を子要素のとりうる最大の横幅となるよう`width: max-content`を指定します。

なお、max-contentはブラウザによって対応状況が異なります。
[Intrinsic & Extrinsic Sizing | Can I use...](https://caniuse.com/#search=max-content)


#### th, tdタグ

```css
.sticky_table th,
.sticky_table td {
  display: inline-block;
  width: 8.0rem;
  background: #fff;
  font-size: 1.0rem;
}
```

thとtdタグは`display: inline-block`とし、横並びに整列させ、`width`で横幅を固定します。

`background`はスクロールした際、固定要素と文字が重なるのを回避するため指定します。

#### 列ヘッダの固定

```css
.sticky_table tbody th {
  position: -webkit-sticky;
  position: sticky;
  left: 0;
  z-index: 1;
}
```

列はtbodyの子要素thに対し`position: sticky`を指定します。スクロールの際に左側に吸着するよう`left: 0`を指定します。

#### 行ヘッダの固定

```css
.sticky_table thead {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 2;
}
```

行はtheadに対し`position: sticky`を指定します。スクロールの際に上側に吸着するよう`top: 0`を指定します。

#### 行・列ヘッダの交差部分の固定

```css
.sticky_table thead th.blank {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  left: 0;
  z-index: 3;
}
```

行・列ヘッダの交差部分はtheadのblankクラスを指定したthに対し`position: sticky`を指定します。スクロールの際に左上に吸着するよう`top: 0; left: 0`を指定します。

***

ヘッダ固定テーブルの実装は以上です。

あとはCSSでデザインを行えば完成ですが、ここではヘッダ固定テーブルの実装について記すことが主旨のため、デザイン関連のコードは割愛させていただきます。[Codepen](https://codepen.io/s0tter/pen/ayQOJR)をご参照ください。

## Conclusion

CSSのみでヘッダ固定テーブルが実装できましたがこれで完成ではありません。

当初目的としていたものは、`position: sticky`の指定のみでtheadとtbody>thを固定する事でした。しかしながら各ブラウザの対応状況が異なるため、`display`属性をブロック要素にして対処しています。

ブロック要素を横並びにするため`width: max-content`が必要になります。ブラウザの対応状況が進み、table部品にも`position: sticky`が適用できるようになれば不要となり、より容易にヘッダ固定テーブルを実装することができるようになるでしょう。


## Reference

[CSS position: sticky | Can I use...](https://caniuse.com/#search=sticky)
[Intrinsic & Extrinsic Sizing | Can I use...](https://caniuse.com/#search=max-content)