【CSS】position: stickyが効かない原因と解決方法

【CSS】position: stickyが効かない原因と解決方法 HTML/CSS

スクロールに追従する見出しやサイドナビを作る際に便利な position: sticky。しかし「効かない」「固定されない」ケースは少なくありません。多くは指定忘れや親要素のレイアウト条件が原因です。代表的な落とし穴と、確実に動かすためのチェックポイントを順に解説します。

原因1:オフセット(top など)の未指定

position: sticky だけでは動作しません。ビューポートやスクロールコンテナのどこに張り付くかを示すオフセット(top / inset-block-start 等)を必ず指定します。

/* NG: オフセットがない */
.sticky {
  position: sticky;
}

/* OK: 上端に張り付く */
.sticky {
  position: sticky;
  top: 0;
}

原因2:スクロールしている要素が違う

スティッキーは「もっとも近いスクロール可能な祖先」(overflowauto / scroll / hidden)を基準に張り付きます。ページ全体ではなく、内側のボックスがスクロールしていると想定と異なる挙動になります。意図した要素がスクロール元になっているかを確認し、必要に応じてオーバーフロー設定を調整します。

/* 親に overflow があると、その親が基準のスクロールコンテナになる */
.container {
  height: 300px;
  overflow: auto; /* ここがスクロール元になる */
}
.sticky {
  position: sticky;
  top: 0;
}

ページ全体に対して張り付かせたいのに、意図せず親に overflow が設定されていると、コンテナの上端で止まってしまいます。不要な overflow は外すか、スクロール元を明示的に設計し直してください。

原因3:祖先の overflow が sticky を遮っている

祖先要素に overflow: hiddenclip があると、スティッキーの見え方が切り取られ、張り付いても視覚的に動いていないように見えることがあります。特にヘッダー直下のラッパーに overflow: hidden を当てるケースで起こりがちです。可能なら overflow: visible に戻すか、構造を分けてクリッピング対象から外します。

原因4:要素や親の高さ・マージン都合で張り付く前に終わっている

スティッキーは「通常フローでの位置」から「オフセット位置」までの間でのみ固定されます。要素自体が高すぎる、親の高さが足りない、縦方向のマージンの収縮や gap の影響で計算上の余白が足りない場合、張り付く前にスクロール範囲が終わります。親の高さを十分に確保し、不要な縦マージンを調整します。

原因5:テーブル行や一部要素では期待通りに動かない

ブラウザ間の実装差やレイアウト仕様上、<thead><tr><th> などのテーブル内部、または display: table 相当のボックスでは安定しないことがあります。見出し固定はテーブル外にヘッダー行を複製する、もしくはグリッドや通常のブロック要素に置き換えて実装するのが確実です。

原因6:transform / filter / contain などで新しいコンテキストを作っている

祖先に transformfiltercontain: paint などを付与すると、新しいコンテキスト(コンテインニングブロック)が形成され、意図せぬ基準で振る舞うことがあります。原因切り分けとして、これらの指定を一旦外して動くか確認し、必要最小限の要素に限定しましょう。

原因7:z-index の不足で背面に隠れている

スティッキー自体は張り付いていても、上に重なる要素があり見えないケースがあります。特に固定ヘッダー(position: fixed)と併用時は、z-index を適切に設定して重なり順を制御します。

header.fixed {
  position: fixed;
  top: 0;
  z-index: 1000;
}
.sticky {
  position: sticky;
  top: 60px;   /* 固定ヘッダーの高さ分ずらす */
  z-index: 500; /* 必要に応じて調整 */
}

原因8:期待と異なる書き方(flex/grid と混在時)

フレックスやグリッド内でも position: sticky は動作しますが、親に前述の overflow がある、列の高さが自動計算で不足する、align-content などでレイアウトが圧縮されると、張り付き位置に到達しないことがあります。列・行のサイズ(min-heightgrid-auto-rows)を見直し、スクロール量を確保します。

最小構成での動作確認用コード

まずは余計なスタイルを排して「動く最小例」で確認し、そこから本番レイアウトに戻すと原因が特定しやすくなります。

<style>
  body { margin: 0; }
  .spacer { height: 1200px; background: #f4f4f4; }
  .sticky {
    position: sticky;
    top: 0;
    background: #fff;
    border-bottom: 1px solid #ddd;
    padding: 12px 16px;
  }
</style>

<div class="sticky">見出しがスクロールで張り付く</div>
<div class="spacer"></div>

チェックリスト(文章で要点確認)

オフセット(top など)を必ず指定する。張り付きの基準となるスクロールコンテナを把握し、不要な overflow を外す。祖先の overflow: hiddentransform を疑い、外して検証する。親や自身の高さ・マージンで実質的なスクロール余地が失われていないかを見る。固定ヘッダーと重なる場合は topz-index を調整する。テーブル内部など不安定な箇所では構造を置き換える。

まとめ

position: sticky は「オフセットの指定」「スクロールコンテナの理解」「祖先のオーバーフロー・コンテキストの影響」を押さえれば安定して動作します。うまくいかないときは最小構成で再現し、上から順に条件を満たしていくのが最短の解決方法です。