*今回はコーヒーサンプルサイトで実装した複雑なtable表の解説です

実装の解説

全体構造
このテーブルは2つの独立したテーブルをFlexboxで横並びにすることで、左列固定・右列スクロール可能を実現しています。


┌─────────────────────────────────────┐
│ table-wrapper (display: flex)       │
│ ┌──────────┬────────────────────┐  │
│ │ 固定列    │ スクロール可能列    │  │
│ │ table-   │ table-scroll       │  │
│ │ fixed    │ (overflow-x: auto) │  │
│ └──────────┴────────────────────┘  │
└─────────────────────────────────────┘




HTMLの構造

1. 親コンテナ(Flexbox)

<div class="table-wrapper">
  <!-- 2つのテーブルを横並びに配置 -->
</div>


2. 左側:固定列テーブル
<div class="table-fixed">
  <table class="price-table-left">
    <thead>
      <tr>
        <th>コーヒー豆の品名</th>
      </tr>
    </thead>
    <tbody>
      <tr><td>ブラジリアン</td></tr>
      <tr><td>タンザ</td></tr>
      <!-- 7行分 -->
    </tbody>
  </table>
</div>


1列のみ(品名だけ)
スクロールしても画面に固定される




3. 右側:スクロール可能テーブル


<div class="table-scroll">
  <table class="price-table-right">
    <thead>
      <tr>
        <th rowspan="2">豆色</th>
        <th rowspan="2">産地</th>
        <th colspan="8">ブロック料金</th>
        <th rowspan="2">1カ月購入<br>4500g</th>
      </tr>
      <tr>
        <!-- colspan分の詳細 -->
        <th>500g</th>
        <th>1000g</th>
        <!-- ... 8列 -->
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>明るい</td>
        <td>サンパウロ</td>
        <td>900円</td>
        <!-- ... 9列分の価格 -->
      </tr>
      <!-- 7行分 -->
    </tbody>
  </table>
</div>




CSSの設計

1. Flexboxで横並び

.table-wrapper {
  display: flex;
  width: 100%;
}


→ 2つのテーブルを横に配置

2. 左側を固定

.table-fixed {
  flex-shrink: 0;  /* 縮小禁止 */
}

→ 左テーブルの幅は変わらない



3. 右側をスクロール可能に

.table-scroll {
  flex-grow: 1;           /* 残りのスペースを使う */
  overflow-x: auto;       /* 横スクロール有効 */
  scrollbar-width: none;  /* スクロールバー非表示(Firefox) */
  -ms-overflow-style: none; /* IE/Edge */
}

.table-scroll::-webkit-scrollbar {
  display: none;  /* Chrome/Safari */
}




4. 高さの統一

.price-table-left thead tr:first-child th {
  height: 155px;  /* 右側の2行分と同じ高さ */
  vertical-align: middle;
}



5. 共通スタイル

.price-table-left,
.price-table-right {
  border-collapse: collapse;
  font-size: 14px;
}

.price-table-left tbody td,
.price-table-right tbody td {
  padding: 12px 10px;
  border: 1px solid #ddd;
}




*rowspan/colspanの使い方
rowspan="2"(2行にまたがる)

<th rowspan="2">豆色</th>
<th rowspan="2">産地</th>

→ ヘッダーの1行目と2行目を縦に結合



colspan="8"(8列にまたがる)

<th colspan="8">ブロック料金</th>

→ 8列分を横に結合してタイトル表示




JavaScriptのドラッグスクロール

// テーブルのドラッグスクロール機能
document.addEventListener("DOMContentLoaded", function () {
  const scrollContainer = document.querySelector(".table-scroll");

  if (!scrollContainer) return;

  let isDown = false;
  let startX;
  let scrollLeft;

  // マウスカーソルをグラブ(手のひら)に変更
  scrollContainer.style.cursor = "grab";

  scrollContainer.addEventListener("mousedown", (e) => {
    isDown = true;
    scrollContainer.style.cursor = "grabbing";
    startX = e.pageX - scrollContainer.offsetLeft;
    scrollLeft = scrollContainer.scrollLeft;
  });

  scrollContainer.addEventListener("mouseleave", () => {
    isDown = false;
    scrollContainer.style.cursor = "grab";
  });

  scrollContainer.addEventListener("mouseup", () => {
    isDown = false;
    scrollContainer.style.cursor = "grab";
  });

  scrollContainer.addEventListener("mousemove", (e) => {
    if (!isDown) return;
    e.preventDefault();
    const x = e.pageX - scrollContainer.offsetLeft;
    const walk = (x - startX) * 2; // スクロール速度(数値を大きくすると速くなる)
    scrollContainer.scrollLeft = scrollLeft - walk;
  });
});



動作:

テーブル上でマウスクリック
左右にドラッグ
スクロール位置が連動して移動
カーソルはgrab(開いた手)→ grabbing(掴んでいる手)に変化



なぜこの方法が優れているか


❌ position: sticky の問題点
ブラウザ互換性が不安定
border-collapseと相性が悪い
複雑なヘッダー(rowspan/colspan)で崩れる


✅ 2テーブル分割の利点
確実に動作(全ブラウザ対応)
スクロール制御が簡単(overflow-xだけ)
レスポンシブ対応しやすい
独立したスタイル管理が可能


レスポンシブ対応

@media screen and (max-width: 768px) {
  .price-table-left thead tr:first-child th {
    height: 128px; /* スマホでは低く */
  }
  .price-table-left, .price-table-right {
    font-size: 12px; /* 文字を小さく */
  }
}



まとめ
この実装の核心は:

テーブルを2つに分割(固定列 + スクロール列)
Flexboxで横並び(display: flex)
行数とセルサイズを完全一致させる
JavaScriptでドラッグスクロール追加
→ 左列が常に見える使いやすいテーブルが完成


下が一層table