• #CSS
  • #Flexbox
  • #sticky
  • #テーブル
開発未分類メモ

CSSでテーブルヘッダーを固定する(position: stickyが効かない時の対処法)

結論

Flexboxレイアウト内で position: sticky が効かない場合、以下の2点を確認する。

  1. 親要素に min-height: 0 を追加する(Flexboxの高さ制約を伝播させる)
  2. スクロールコンテナを明確にするoverflow: auto を持つ要素を特定)
/* 親コンテナ(Flexアイテム)*/
.parent {
  flex: 1;
  min-height: 0;      /* これが重要 */
  display: flex;
  flex-direction: column;
}

/* テーブルを包むコンテナ */
.table-wrapper {
  flex: 1;
  overflow: auto;     /* ここがスクロールコンテナ */
}

/* テーブルヘッダー */
thead {
  position: sticky;
  top: 0;
  z-index: 1;
}

症状

Nuxt/Vueで作成したテーブルコンポーネントで、theadposition: sticky; top: 0 を設定したが、スクロールしてもヘッダーが固定されない。

さらに悪いケースでは、ヘッダー行の上にデータ行が表示されてしまう「ブサイク」な状態になった。

原因

position: sticky の動作条件

position: sticky は、最も近いスクロール祖先overflow: auto または overflow: scroll を持つ要素)に対して固定される。

スクロール祖先が正しく設定されていないと、stickyは機能しない。

Flexboxと高さの問題

Flexboxレイアウトでは、子要素のデフォルトの min-heightauto になる。これにより、コンテンツの高さがそのまま使われ、親の高さ制約が伝播しない。

[viewport] height: 100vh
    ↓ 高さ制約
[flex container] flex: 1
    ↓ min-height: auto だと伝播しない!
[flex item] → コンテンツの高さがそのまま使われる
    ↓
[table] → スクロールが発生しない → stickyも効かない

解決方法

1. Flex階層に min-height: 0 を追加

高さ制約を伝播させるため、Flexアイテムに min-height: 0 を追加する。

/* Before */
.detail-pane {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* After */
.detail-pane {
  flex: 1;
  min-height: 0;      /* 追加 */
  display: flex;
  flex-direction: column;
}

2. スクロールコンテナを設定

テーブルを包むコンテナに overflow: auto を設定し、明確なスクロールコンテナにする。

.simulation-table {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.table-container {
  flex: 1;
  overflow: auto;     /* スクロールコンテナ */
}

3. theadにstickyを設定

スクロールコンテナが正しく設定されていれば、thead のstickyが機能する。

.yearly-table thead {
  position: sticky;
  top: 0;
  z-index: 9;
}

/* ヘッダーセルに背景色を設定(透過防止)*/
.yearly-table thead th {
  background: #1f2937;
}

修正前後の比較

Before(効かない)

[.detail-content] overflow: auto
    ↓ スクロールはここで発生するはず...
[.simulation-table] overflow: visible(デフォルト)
    ↓ しかし高さ制約が伝播しない
[table]
    [thead] position: sticky → 効かない

After(効く)

[.detail-pane] min-height: 0 ← 追加
    ↓ 高さ制約が伝播
[.simulation-table] height: 100%, overflow: hidden
    ↓
[.table-container] flex: 1, overflow: auto ← スクロールコンテナ
    ↓
[table]
    [thead] position: sticky, top: 0 → 効く!

デバッグ方法

スティッキーが効かない場合、以下をブラウザのDevToolsで確認する。

// スクロールコンテナの状態を確認
const container = document.querySelector('.table-container');
console.log({
  scrollHeight: container.scrollHeight,  // コンテンツの高さ
  clientHeight: container.clientHeight,  // 表示領域の高さ
  overflow: getComputedStyle(container).overflow
});

// scrollHeight > clientHeight ならスクロール可能

scrollHeightclientHeight が同じ場合、スクロールが発生していないため、stickyも機能しない。

まとめ

position: sticky が効かない場合のチェックリスト。

  1. スクロールコンテナ(overflow: auto)は正しく設定されているか
  2. Flexboxの親要素に min-height: 0 は設定されているか
  3. scrollHeight > clientHeight になっているか(スクロールが発生するか)
  4. sticky要素に背景色は設定されているか(透過して見えていないか)