【サンプルコード付】テーブルの縦と横のヘッダーを固定して、縦横スクロールに対応する方法を解説

# はじめに
こんにちは、Webエンジニアの岩田史門([@SI_Monxy](https://x.com/SI_Monxy))です！
今回はテーブルのヘッダー固定縦横スクロールについて記事を書いてみました！
改善点や修正点があれば、コメントにて優しくご指導いただけると嬉しいです！！

# 概要
Webページで表を表示する際に、データの可読性を向上させるために縦と横のヘッダーを固定することがよくあります。これにより、ユーザーは表の内容をスクロールしても、常にヘッダーを参照することができます。本記事では、HTMLとCSSを使用して、テーブルのヘッダーを固定し、縦横スクロールに対応する方法について解説します。
下記のようなテーブルを作成できるサンプルコードを記載しているので、気になる方はぜひ実際に試してみてください！
![ヘッダー固定の縦横スクロール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647998/34cee68c-fc7a-d05b-18e0-3892f78879b9.png)

# サンプルコードと解説
## 全体のサンプルコード
まず、基本的なHTML構造を確認しましょう。
以下のように、テーブルのヘッダーとデータを定義します。
実際に動かしてみて見るとイメージがつきやすいと思います！
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fixed Columns and Rows Table</title>
<style>
    #wrapper {
        max-height: 240px; /* テーブルの高さを制限*/
        max-width: 400px; /*テーブルの高さを制限  */
        overflow: auto; /* スクロールを有効にする */
        position: relative;
    }

    th {
        background-color: lavenderblush; /* ヘッダーの背景色を設定 */
        position: sticky;
        top: 0;
        z-index: 2;
    }
    th.vertical-header {
        position: sticky;
        left: 0;
        z-index: 1;
    }
    th.special-header {
        background-color: lavenderblush; 
        top: 0;
        left: 0;
        z-index: 3;
        position: sticky;
    }

</style>
</head>
<body>

<div id="wrapper">
    <table>
        <thead>
            <tr>
                <th class="special-header">cell_01-01</th> <!-- 縦ヘッダー -->
                <th>cell_01-02</th>
                <th>cell_01-03</th>
                <th>cell_01-04</th>
                <th>cell_01-05</th>
                <th>cell_01-06</th>
                <th>cell_01-07</th>
                <th>cell_01-08</th>
                <th>cell_01-09</th>
                <th>cell_01-10</th>
            </tr>
        </thead>
        <tbody>
            <!-- 表のデータ -->
            <tr>
                <th class="vertical-header">cell_02-01</th> <!-- 縦ヘッダー -->
                <td>cell_02-02</td>
                <td>cell_02-03</td>
                <td>cell_02-04</td>
                <td>cell_02-05</td>
                <td>cell_02-06</td>
                <td>cell_02-07</td>
                <td>cell_02-08</td>
                <td>cell_02-09</td>
                <td>cell_02-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_03-01</th> <!-- 縦ヘッダー -->
                <td>cell_03-02</td>
                <td>cell_03-03</td>
                <td>cell_03-04</td>
                <td>cell_03-05</td>
                <td>cell_03-06</td>
                <td>cell_03-07</td>
                <td>cell_03-08</td>
                <td>cell_03-09</td>
                <td>cell_03-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_04-01</th> <!-- 縦ヘッダー -->
                <td>cell_04-02</td>
                <td>cell_04-03</td>
                <td>cell_04-04</td>
                <td>cell_04-05</td>
                <td>cell_04-06</td>
                <td>cell_04-07</td>
                <td>cell_04-08</td>
                <td>cell_04-09</td>
                <td>cell_04-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_05-01</th> <!-- 縦ヘッダー -->
                <td>cell_05-02</td>
                <td>cell_05-03</td>
                <td>cell_05-04</td>
                <td>cell_05-05</td>
                <td>cell_05-06</td>
                <td>cell_05-07</td>
                <td>cell_05-08</td>
                <td>cell_05-09</td>
                <td>cell_05-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_06-01</th> <!-- 縦ヘッダー -->
                <td>cell_06-02</td>
                <td>cell_06-03</td>
                <td>cell_06-04</td>
                <td>cell_06-05</td>
                <td>cell_06-06</td>
                <td>cell_06-07</td>
                <td>cell_06-08</td>
                <td>cell_06-09</td>
                <td>cell_06-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_07-01</th> <!-- 縦ヘッダー -->
                <td>cell_07-02</td>
                <td>cell_07-03</td>
                <td>cell_07-04</td>
                <td>cell_07-05</td>
                <td>cell_07-06</td>
                <td>cell_07-07</td>
                <td>cell_07-08</td>
                <td>cell_07-09</td>
                <td>cell_07-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_08-01</th> <!-- 縦ヘッダー -->
                <td>cell_08-02</td>
                <td>cell_08-03</td>
                <td>cell_08-04</td>
                <td>cell_08-05</td>
                <td>cell_08-06</td>
                <td>cell_08-07</td>
                <td>cell_08-08</td>
                <td>cell_08-09</td>
                <td>cell_08-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_09-01</th> <!-- 縦ヘッダー -->
                <td>cell_09-02</td>
                <td>cell_09-03</td>
                <td>cell_09-04</td>
                <td>cell_09-05</td>
                <td>cell_09-06</td>
                <td>cell_09-07</td>
                <td>cell_09-08</td>
                <td>cell_09-09</td>
                <td>cell_09-10</td>
            </tr>

            <tr>
                <th class="vertical-header">cell_10-01</th> <!-- 縦ヘッダー -->
                <td>cell_10-02</td>
                <td>cell_10-03</td>
                <td>cell_10-04</td>
                <td>cell_10-05</td>
                <td>cell_10-06</td>
                <td>cell_10-07</td>
                <td>cell_10-08</td>
                <td>cell_10-09</td>
                <td>cell_10-10</td>
            </tr>
            <!-- 他の行も同様に記述 -->
        </tbody>
    </table>
</div>

</body>
</html>
```

## CSSの設定
### z-indexの使い方
z-indexは、要素のスタッキング順序（重なり順）を指定するプロパティです。大きい値の要素が小さい値の要素の上に表示されます。今回は、ヘッダーが他の要素よりも上に表示されるように設定します。

``` css
th {
    background-color: lavenderblush; /* ヘッダーの背景色を設定 */
    position: sticky;
    top: 0;
    z-index: 2;
}
th.vertical-header {
    position: sticky;
    left: 0;
    z-index: 1;
}
th.special-header {
    background-color: lavenderblush; 
    top: 0;
    left: 0;
    z-index: 3;
    position: sticky;
}
```
### position: sticky;について
position: sticky;は、要素がそのコンテナ内でスクロールに応じて固定される位置を指定します。今回は、ヘッダー行と列を固定するために使用します。

``` css
th {
    background-color: lavenderblush; /* ヘッダーの背景色を設定 */
    position: sticky;
    top: 0;
    z-index: 2;
}
th.vertical-header {
    position: sticky;
    left: 0;
    z-index: 1;
}
th.special-header {
    background-color: lavenderblush; 
    top: 0;
    left: 0;
    z-index: 3;
    position: sticky;
}
```
これにより、ヘッダー行は常に上部に、ヘッダー列は常に左側に固定され、スクロールしても表示され続けます。

### overflowについて
overflowプロパティは、コンテンツがボックスのサイズを超えたときの表示方法を指定します。今回は、テーブル全体をスクロール可能にするために使用します。このとき、max-heightとmax-widthで最大値を設定してあげることがポイントです。ここで設定した最大値以上の高さや幅になったときに、スクロールバーが表示されます。
```css
#wrapper {
    max-height: 240px; /* テーブルの高さを制限 */
    max-width: 400px; /* テーブルの幅を制限 */
    overflow: auto; /* スクロールを有効にする */
    position: relative;
}
```
この設定により、テーブルのサイズを制限し、必要に応じてスクロールバーが表示されるようになります。

# まとめ
以上の方法を組み合わせることで、縦と横のヘッダーが固定され、スクロール可能なテーブルを実現できます。これにより、大量のデータを表示する際の可読性が向上し、ユーザーエクスペリエンスも向上します。

この技術は、データの可視化や管理画面の作成に非常に有用ですので、ぜひ活用してみてください。

# 参考
- [MDN Web Docs - position | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/position)
- [MDN Web Docs - z-index | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index)
- [MDN Web Docs - overflow | MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow)