テーブルのヘッダー(thead)を固定してtbody内を縦スクロールさせる
theadを固定してtbody内をスクロール+セルの幅の自動調整を兼ね備えたちょうどいい感じのテーブルを実装するのに一手間必要だったのでまとめておきます。
はじめに
今回の記事ではこういう形のHTMLを使うことを想定しています。
<table class="table-scroll">
<thead>
<tr>
<th>Date</th>
<th>Blog Title</th>
<th>Author</th>
<tr>
</thead>
<tbody>
<tr>
<td>March 12, 2024</td>
<td>Title lorem ipsum dolor exsistit autem hoc loco quaedam</td>
<td>inari-tech</td>
<tr>
<tr>
<td>March 12, 2024</td>
<td>Title lorem ipsum dolor exsistit autem hoc loco quaedam</td>
<td>inari-tech</td>
<tr>
<tr>
<td>March 12, 2024</td>
<td>Title lorem ipsum dolor exsistit autem hoc loco quaedam</td>
<td>inari-tech</td>
<tr>
</tbody>
</table>
HTMLとCSSだけでできるスクロールテーブル
thead
とtbody
にdisplay: block
を指定して、tbody
にheight
とoverflow-y: scroll
を指定するだけで、tbody内をスクロールさせることはできます。
thead, tbody {
display: block;
}
tbody {
height: 100px;
overflow-y: scroll;
}
しかしこれだと見てわかるように、thead
とtbody
にdisplay: block;
を指定した為、横幅が自動調整されずレイアウトが崩れてしまいます。
そこでth
とtd
にwidthを指定します。
thead, tbody {
display: block;
}
tbody {
height: 100px;
overflow-y: scroll;
}
th, td {
width: 200px;
}
結果
これでHTMLとCSSだけでヘッダー固定のスクロールテーブルが実装できました!
幅自動調整機能付きスクロールテーブルの実装
セルの横幅が一定で良い場合は上記の方法で大丈夫なんですが、今回の例だとBlog Titleに入る文字数が一番多くなるので、Blog Titleのカラムだけ少し幅が広くなるのが理想です。
テーブルに入る内容が動的に変わる場合などはCSSで一つ一つ幅を調整するのも難しいので、th
もしくはtd
に入っている文字数によってカラムの横幅を自動調整する関数を作ってみました。
JavaScript
const adjustCellWidth = (dom: any) => {
try {
const _tHead = dom.getElementsByTagName("thead");
const _tBody = dom.getElementsByTagName("tbody");
if (!_tHead || !_tBody || _tHead.length <= 0 || _tBody.length <= 0) {
return;
}
const tHead = _tHead[0];
const tBody = _tBody[0];
const _headRow = tHead.getElementsByTagName("tr");
const _bodyRow = tBody.getElementsByTagName("tr");
if (
!_bodyRow ||
!_headRow ||
_bodyRow.length <= 0 ||
_headRow.length <= 0
) {
return;
}
const headRow = _headRow[0];
const bodyRow = _bodyRow[0];
let total = 0;
let sizes = [];
const tableWidth = dom.clientWidth;
/* サイズを取得 */
for (var i = 0; i < headRow.children.length; i++) {
const th = headRow.children[i];
const td = bodyRow.children[i];
const w = th.clientWidth > td.clientWidth ? th.clientWidth : td.clientWidth;
sizes.push(w);
total = total + w;
}
/* サイズをセット */
for (let i = 0; i < sizes.length; i++) {
const per = sizes[i] / total;
const amount = tableWidth * per;
const th = headRow.children[i];
const td = bodyRow.children[i];
th.style.width = `${amount}px`;
td.style.width = `${amount}px`;
}
} catch (e) {
console.error(e);
}
};
document.addEventListener("DOMContentLoaded", () => {
const tables = document.querySelectorAll("table.table-scroll");
if (tables && tables.length !== 0) {
tables.forEach((e) => {
adjustCellWidth(e);
});
}
});
まずはthead
のtr
とtbody
のtr
を取得し、それぞれのtr
に含まれるセルの長さを比較して、大きい方のサイズにもう一方のサイズを合わせる処理を行っています。
次に一つのtr
に含まれるセルの横幅の合計値に対してそのセルが占める割合を取得し、それでtable
の横幅を割った値を最終的にセルの横幅として指定しています。
これによってtable
にwidth
が指定された場合もtable
の横幅ぴったりにセルが埋まるようになっています。
CSS
table.table-scroll {
min-width: 100%;
thead,
tbody {
display: block;
}
tbody {
min-width: 100%;
overflow-y: scroll;
}
th,
td {
min-width: 80px;
}
}
自動調整された結果th
とtd
が小さくなりすぎないように、min-width
を指定しました。ここは適時調整してください。
結果
まさにやりたかった感じになりました🎉
ReactとTailwindCSSでコンポーネント化しました
ReactとTailwindCSSでコンポーネント化してみたので、実例が見てみたい方はこちらから実際に動いているところを見てみてください。
こちらの例ではReactとTailwindCSSを使っているので書き方は変わってますが、基本的にやっていることはこの記事の内容と同じです。