テーブルのヘッダー(thead)を固定してtbody内を縦スクロールさせる
2024/05/11

テーブルのヘッダー(thead)を固定してtbody内を縦スクロールさせる

4 mins to read

theadを固定してtbody内をスクロール+セルの幅の自動調整を兼ね備えたちょうどいい感じのテーブルを実装するのに一手間必要だったのでまとめておきます。

はじめに

今回の記事ではこういう形のHTMLを使うことを想定しています。

index.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だけでできるスクロールテーブル

theadtbodydisplay: blockを指定して、tbodyheightoverflow-y: scrollを指定するだけで、tbody内をスクロールさせることはできます。

style.css
thead, tbody {
  display: block;
}
tbody {
  height: 100px;
  overflow-y: scroll;
}

しかしこれだと見てわかるように、theadtbodydisplay: block;を指定した為、横幅が自動調整されずレイアウトが崩れてしまいます。

そこでthtdにwidthを指定します。

style.css
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);
    });
  }
});

まずはtheadtrtbodytrを取得し、それぞれのtrに含まれるセルの長さを比較して、大きい方のサイズにもう一方のサイズを合わせる処理を行っています。

次に一つのtrに含まれるセルの横幅の合計値に対してそのセルが占める割合を取得し、それでtableの横幅を割った値を最終的にセルの横幅として指定しています。

これによってtablewidthが指定された場合もtableの横幅ぴったりにセルが埋まるようになっています。

CSS

table.table-scroll {
  min-width: 100%;
  thead,
  tbody {
    display: block;
  }
  tbody {
    min-width: 100%;
    overflow-y: scroll;
  }
  th,
  td {
    min-width: 80px;
  }
}

自動調整された結果thtdが小さくなりすぎないように、min-widthを指定しました。ここは適時調整してください。

結果

まさにやりたかった感じになりました🎉

ReactとTailwindCSSでコンポーネント化しました

ReactとTailwindCSSでコンポーネント化してみたので、実例が見てみたい方はこちらから実際に動いているところを見てみてください。

こちらの例ではReactとTailwindCSSを使っているので書き方は変わってますが、基本的にやっていることはこの記事の内容と同じです。