Web開発をしていると、時々「コードは正しいはずなのに、なぜか表示が崩れる」という不可解な現象に出会います。今回は、まさにそんなCSSの罠にハマり、数日間にわたって格闘した末に解決した「追従ヘッダーの表示崩れバグ」について、その調査の経緯と解決策を共有します。

スポンサーリンク

現象:テーブルの2行目がヘッダーに重なる!?

私が開発しているポートフォリオ分析アプリに、以下のような画面があります。

  • 左にサマリー、右に保有銘柄一覧テーブルを配置した2カラムレイアウト
  • 右の一覧は縦に長くなるため、スクロールしてもヘッダーが見えるようにしたい

そこで、一覧のタイトル部分(.card-header)とテーブルヘッダー(thead)の両方に position: sticky を設定し、ダブル追従ヘッダーを実装しようとしました。

しかし、実際に表示してみると、スクロール時にテーブルの2行目の内容がヘッダー部分に重なって表示されるという奇妙なバグが発生してしまいました

調査の迷宮:犯人はJavaScriptか?

最初、私はJavaScriptのテーブル描画ロジックに原因があると思い込み、調査を開始しました

試行錯誤①:DOMのクリア処理

「テーブルを再描画する際に、古いDOMが残ってしまっているのでは?」と考え、テーブルの中身をクリアする処理を様々な方法で試しました。

  • innerHTML = '' で一括クリア
  • while (table.firstChild) ループで子要素を一つずつ removeChild
  • テーブル自体を一度削除して再生成

しかし、結果は変わらず。バグは頑固に居座り続けました。

試行錯誤②:テーブルの再構築ロジック

次に、テーブルの構造を組み立てる処理を疑いました。createTHead(), createTBody(), insertRow(), insertCell()といったAPIの使い方が悪いのかと、ドキュメントを何度も見直しました。

最終的には、「HTML文字列を一度に組み立ててから innerHTMLに一括で代入する」というリファクタリングまで行いましたが、それでも現象は解決しませんでした。

この時点で、「原因はJavaScriptではない」と確信しました。DOM構造は開発者ツールで見ても完全に正常なのに、表示だけがおかしいのです。

灯台下暗し:真犯人はCSSだった

JavaScriptへの疑いが晴れた私は、改めてCSSに目を向けました。そして、ついに原因を発見します。

問題のCSSコードがこちらです。

/* カードのヘッダー(タイトル部分) */
.card-header {
    position: sticky;
    top: 1.5rem;
    z-index: 2;
}

/* テーブルのヘッダーセル */
#analysis-table thead th {
    position: sticky;
    top: calc(1.5rem + 65px); /* カードヘッダーの下に固定 */
    z-index: 1;
}

原因は、position: sticky の入れ子(nested)構造でした。
sticky な .card-header の中に、さらに sticky な thead th が存在していたのです。このような複雑な stickyの入れ子は、ブラウザが各要素の正しい表示位置を計算するのを混乱させ、予期せぬレンダリングのバグを引き起こすことがあるようでして、今回まさにその罠にかかっていたようです。

スポンサーリンク

解決策:stickyの入れ子をやめる

解決策はシンプルでした。sticky の入れ子構造を解消することです。

  1. カードヘッダーの position: sticky を解除する。
  2. テーブルヘッダーの sticky 指定を、各セル(th)からヘッダー全体を囲むタグに移動させ、より安定させる。

修正後のCSSがこちらです。

/* カードのヘッダー(タイトル部分) */
.card-header {
    /* position: sticky; を削除 */
 }
/* テーブルヘッダー全体を固定 */
#analysis-table thead {
    position: sticky;
    top: 0; /* スクロールコンテナの最上部に固定 */
     z-index: 1;
 }
 
 #analysis-table thead th {
     /* theadに指定を移したため、個別のsticky指定は不要 */
     background-color: #f1f3f5;
 }

この修正により、sticky のコンテキストが単純化され、ブラウザは正しくテーブルヘッダーの位置を計算できるようになりました。結果として、あの頑固だった表示崩れバグは、嘘のように綺麗に解消されたのです。

まとめと教訓

今回の経験から得られた教訓は以下の通りです。

  1. DOM構造が正しいのに表示が崩れる場合、CSSを疑え。 特に position, display, z-index, overflowといったレイアウト関連のプロパティは要注意です。
  2. position: sticky は非常に便利だが、入れ子構造にすると危険。 複雑なレイアウトで sticky
    を使う際は、できるだけ構造をシンプルに保つことが重要です。
  3. 問題の切り分けが解決への近道。 最初から「JavaScriptか、CSSか」という視点で問題を切り分けていれば、もっと早く解決できたかもしれません。


この記事が気に入ったら『目黒で働く分析担当の作業メモ』ご支援をお願いします!

※OFUSEに飛びます


おすすめの記事