はじめに
最近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を意識しています。
<divclass="table-wrapper"><tableclass="table text-nowrap sticky-table table-borderless"><theadclass="thead-light"><trclass="fixed-header-0"><thclass="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><trclass="fixed-header-1"><thclass="fixed-column-0">曜日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th><th>日</th></tr></thead><tbody><tr><thclass="fixed-column-0 table-light">AM</th><td>〇</td><td>〇</td><td>〇</td><td>×</td><td>×</td><td>〇</td><td>〇</td></tr><tr><thclass="fixed-column-0 table-light">PM</th><td>×</td><td>〇</td><td>〇</td><td>×</td><td>〇</td><td>×</td><td>〇</td></tr><tr><thclass="fixed-column-0 table-light">夜</th><td>〇</td><td>〇</td><td>〇</td><td>〇</td><td>〇</td><td>〇</td><td>×</td></tr></tbody></table></div>
ゴールは上記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を作成します。
/*ラッパー*/div.table-wrapper{overflow:scroll;max-height:200px;/*任意*/max-width:400px;/*任意*/}/*行ヘッダーを固定する。topの値はjsで動的に指定*/table.sticky-tabletheadtr[class*="fixed-header-"]th{position:-webkit-sticky;/* for Safari */position:sticky;/* tbody tdより手前に表示する */z-index:1;}/*行ヘッダーと列ヘッダーが重なる部分を固定する。top,leftの値はjsで動的に指定*/table.sticky-tabletheadtr[class*="fixed-header-"]th[class*="fixed-column-"]{/* 全てのセルより手前に表示する */z-index:2;}/*列ヘッダーを固定する。leftの値はjsで動的に指定*/table.sticky-tabletbodyth[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で書いてしまった。
//行ヘッダーに対しtopを設定height=0;for(vari=0;i<fixed_header_num;i++){$(".fixed-header-"+i+" th").css('top',height);height+=$(".fixed-header-"+i+" th").outerHeight();}//列ヘッダーに対しleftを設定width=0;for(varj=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
<html><head><metaname="viewport"content="width=device-width,initial-scale=1"><metacharset="utf-8"/><title>ヘッダー固定</title><!--bootstrap--><linkrel="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--><linkrel="stylesheet"href="static/css/sticky-table.css"></head><body><!--https://stackoverflow.com/questions/15811653/table-with-fixed-header-and-fixed-column-on-pure-css--><divclass="container"><divclass="table-wrapper"><tableclass="table text-nowrap sticky-table table-borderless"><theadclass="thead-light"><trclass="fixed-header-0"><thclass="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><trclass="fixed-header-1"><thclass="fixed-column-0">曜日</th><th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th><th>日</th></tr></thead><tbody><tr><thclass="fixed-column-0 table-light">AM</th><td>〇</td><td>〇</td><td>〇</td><td>×</td><td>×</td><td>〇</td><td>〇</td></tr><tr><thclass="fixed-column-0 table-light">PM</th><td>×</td><td>〇</td><td>〇</td><td>×</td><td>〇</td><td>×</td><td>〇</td></tr><tr><thclass="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">//固定するヘッダーの数varfixed_header_num=2;//固定するカラムの数varfixed_column_num=1;</script><script src="static/js/sticky-table.js"></script></body></html>
これで任意の数の行/列ヘッダーをcss/jsのみで固定することができます。
課題
Chromeでは動作確認しましたが、IEだとpolyfillが必要みたいです。
stickyfill
また、globalで変数指定が必要だったり、洗練されてないイメージもあるので、もっとうまいやり方がありましたらご教示頂ければ幸いです。
参考
Table with fixed header and fixed column on pure css
stickyfill