LoginSignup
7
6

More than 3 years have passed since last update.

[CSS/jsのみ]tableの行/列ヘッダーを固定する

Last updated at Posted at 2020-03-24

はじめに

最近Webアプリ周辺の技術を学び始めた者です。普段は製造業で設計業務を担当しておりますが、社内システム構築用にいろいろと勉強しています。主に使うのは下記。

  • 言語 : python/C#
  • Web framework : django

記事に書き出すことで自身の理解も深まると考え、今回初投稿をさせて頂きます。

動機

休日に妻と共同でアプリ開発をしています(この開発記録もつけられたらなーと思っています)。
tableを多用するのですが、その際列ヘッダーと行ヘッダーを固定してtbodyのデータセルだけスクロールできないかなと考え、いろいろ調べていました。
便利なプラグインもたくさんありましたが、

  • 複数ヘッダーを固定できるものが限られていた
  • なるべく既存のtableに変更を加えたくない

という理由で手を出せず。position:stickyというステキなオプションがあるので、どうにかこれを使ってできないかと思い、やってみました。
なおこちらのstackoverflowの質問を参考にしました→Table with fixed header and fixed column on pure css

html

下記のようなtableと、wrapperとなるdivを用意します。class名はbootstrapを意識しています。

sample.html
<div class="table-wrapper">
    <table class="table text-nowrap sticky-table table-borderless">
        <thead class="thead-light">
            <tr class="fixed-header-0">
                <th class="fixed-column-0">日付</th>
                <th>1/1</th>
                <th>1/2</th>
                <th>1/3</th>
                <th>1/4</th>
                <th>1/5</th>
                <th>1/6</th>
                <th>1/7</th>
            </tr>
            <tr class="fixed-header-1">
                <th class="fixed-column-0">曜日</th>
                <th></th>
                <th></th>
                <th></th>
                <th></th>
                <th></th>
                <th></th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <th class="fixed-column-0 table-light">AM</th>
                <td></td>
                <td></td>
                <td></td>
                <td>×</td>
                <td>×</td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <th class="fixed-column-0 table-light">PM</th>
                <td>×</td>
                <td></td>
                <td></td>
                <td>×</td>
                <td></td>
                <td>×</td>
                <td></td>
            </tr>
            <tr>
                <th class="fixed-column-0 table-light"></th>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td>×</td>
            </tr>
        </tbody>
    </table>
</div>

image.png

ゴールは上記tableの「日付」「曜日」行を固定しつつ、「AM」「PM」「夜」も常に表示することです。

wrapperにはclass=table-wrapper、tableにはclass=sticky-tableを指定します。また、固定したい行ヘッダーとなる各trには上から順にclass=fixed-header-n(nは0始まりの番号)を指定し、固定したい列ヘッダーとなる各thには左から順にclass=fixed-column-m(mは0始まりの番号)を指定します。
tableの左上の場所は行列方向に拘束したいので、tr.fixed-header-nおよびth.fixed-column-mの両方の指定が必要です。

ちなみにヘッダーにtable-lightやthead-lightで色を付けているのは、透明のままだとヘッダー固定したときに他のセルと重なってしまうからです。

css/js

下記のようなcssとjsを作成します。

sticky-table.css
/*ラッパー*/
div.table-wrapper {
  overflow: scroll;
  max-height:200px; /*任意*/
  max-width:400px; /*任意*/
}

/*行ヘッダーを固定する。topの値はjsで動的に指定*/
table.sticky-table thead tr[class*="fixed-header-"] th {
    position: -webkit-sticky; /* for Safari */
    position: sticky;
    /* tbody tdより手前に表示する */
    z-index: 1;
}

/*行ヘッダーと列ヘッダーが重なる部分を固定する。top,leftの値はjsで動的に指定*/
table.sticky-table thead tr[class*="fixed-header-"] th[class*="fixed-column-"] {
    /* 全てのセルより手前に表示する */
    z-index: 2;
}

/*列ヘッダーを固定する。leftの値はjsで動的に指定*/
table.sticky-table tbody th[class*="fixed-column-"] {
    position: -webkit-sticky; /* for Safari */
    position: sticky;
    /* tbody tdより手前に表示する */
    z-index: 1;
}

コメントを添えていますが、

div.table-wrapper {
  overflow: scroll;
  max-height:200px; /*任意*/
  max-width:400px; /*任意*/
}

はwrapperの挙動です。max-height/max-widthは任意の値に設定してください。

続いて下記のような.jsを作成します。結局jqueryで書いてしまった。

sticky-table.js

//行ヘッダーに対しtopを設定
height = 0;
for (var i = 0; i < fixed_header_num; i++) {
    $(".fixed-header-" + i + " th").css('top', height);
    height += $(".fixed-header-" + i + " th").outerHeight();
}

//列ヘッダーに対しleftを設定
width = 0;
for (var j = 0; j < fixed_column_num; j++) {
    $("th.fixed-column-" + j).css('left', width);
    width += $("th.fixed-column-" + j).outerWidth(true);
}

jsでは固定したい各ヘッダーに対し、「どこまでの位置に達したら上/左方向への移動を拘束するか」の値となるtop/leftの値を動的に設定しています。一番上のヘッダーはtop=0でよいのですが、二番目以降のヘッダーは自身の上にあるヘッダーの累積高さ分の値を設定しています。列ヘッダーも同様。

fixed_header_num, fixed_column_numはそれぞれ、固定したい行/列ヘッダーの数なのですが、これらは使用シーンに合わせて変わると思うので、グローバルで宣言することにします。

使用例

下記のツリー構造を仮定します。

sticky-table/
 ├ sample.html
 └ static/
   ├ css/
   │ └ sticky-table.css
   └ js/
      └ sticky-table.js
sample.html
<html>
<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta charset="utf-8" />
    <title>ヘッダー固定</title>

    <!--bootstrap-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <!--sticky-table-->
    <link rel="stylesheet" href="static/css/sticky-table.css">
</head>
<body>
    <!--https://stackoverflow.com/questions/15811653/table-with-fixed-header-and-fixed-column-on-pure-css-->
    <div class="container">
        <div class="table-wrapper">
            <table class="table text-nowrap sticky-table table-borderless">
                <thead class="thead-light">
                    <tr class="fixed-header-0">
                        <th class="fixed-column-0">日付</th>
                        <th>1/1</th>
                        <th>1/2</th>
                        <th>1/3</th>
                        <th>1/4</th>
                        <th>1/5</th>
                        <th>1/6</th>
                        <th>1/7</th>
                    </tr>
                    <tr class="fixed-header-1">
                        <th class="fixed-column-0">曜日</th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <th class="fixed-column-0 table-light">AM</th>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td>×</td>
                        <td>×</td>
                        <td></td>
                        <td></td>
                    </tr>
                    <tr>
                        <th class="fixed-column-0 table-light">PM</th>
                        <td>×</td>
                        <td></td>
                        <td></td>
                        <td>×</td>
                        <td></td>
                        <td>×</td>
                        <td></td>
                    </tr>
                    <tr>
                        <th class="fixed-column-0 table-light"></th>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td></td>
                        <td>×</td>
                    </tr>
                </tbody>
            </table>
        </div>

    </div>
    <!--jquery+bootstrap-->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

    <!--sticky-table-->
    <script type="text/javascript">
        //固定するヘッダーの数
        var fixed_header_num = 2;

        //固定するカラムの数
        var fixed_column_num = 1;
    </script>
    <script src="static/js/sticky-table.js"></script>
</body>
</html>

image.png

これで任意の数の行/列ヘッダーをcss/jsのみで固定することができます。

課題

Chromeでは動作確認しましたが、IEだとpolyfillが必要みたいです。
stickyfill

また、globalで変数指定が必要だったり、洗練されてないイメージもあるので、もっとうまいやり方がありましたらご教示頂ければ幸いです。

参考

Table with fixed header and fixed column on pure css
stickyfill

7
6
0

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
7
6