Katashin .info

position: sticky が効かない原因を CSS 仕様から読み解く – スクロールコンテナーと包含ブロック

position: sticky は、スクロール時に要素を画面上に固定表示する CSS プロパティです。ヘッダーやナビゲーションバー、目次など、スクロール中も常に表示しておきたい要素の実装によく使われます。

しかし、position: sticky を指定しても要素が固定されない問題に遭遇することがあります。この問題を解決するには、CSS の仕様における要素の配置とスクロールの挙動を理解する必要があります。

この記事では、position: sticky が動作しない主な原因とその解決方法を解説します。

原因1: overflow: hidden による意図しないスクロールコンテナー #

以下の例では、<header> 要素に position: sticky を指定していますが、<article> 要素に overflow: hidden を指定すると固定されなくなります。

<article>
  <header>このヘッダーを固定したい</header>

  記事の本文部分。装飾を本文エリアからはみ出ないようにするために overflow:
  hidden を設定。ヘッダーが固定されなくなる。

  <!-- 装飾 -->
  <img src="/img/tBNP0YBGG6-85.svg" width="85" height="86" alt="" />
</article>
article {
  /*
   * 装飾画像をはみ出ないようにするために overflow: hidden を指定。
   * これが position: sticky が動かなくなる原因。
   */
  overflow: hidden;
  position: relative;
  margin-inline: auto;
  max-width: 400px;
  min-height: 400px;
  border: 1px solid black;
  padding: 8px;
}

header {
  /* ヘッダーを固定するために position: sticky と top: 0 を指定 */
  position: sticky;
  top: 0;
  border: 1px dashed black;
  background-color: white;
}

img {
  position: absolute;
  bottom: -10px;
  right: -10px;
}

この問題は、position: sticky が最も近いスクロールコンテナーに対して固定される仕様に起因します。

スクロールコンテナーとは #

スクロールコンテナーとは、overflow: scrollautohidden のいずれかが指定された要素です。hidden の場合、UI からはスクロールできませんが、JavaScript でスクロール位置を変更できるため、仕様上はスクロールコンテナーとして扱われます。

position: sticky の要素は、最も近いスクロールポート(Nearest Scrollport)、つまり最も近いスクロールコンテナーの可視領域に対して固定されます

この例では、<header> が最も近いスクロールコンテナーである <article> に固定されるため、ページ全体のスクロールには反応しません。

overflow: clip を使う #

overflow: clip は要素からはみ出た部分を非表示にする点では hidden と同じですが、スクロールコンテナーを生成しません。position: sticky と組み合わせる場合は clip を使用することで、期待通りの固定動作を実現できます。

もし position: sticky が機能しない場合は、まず祖先要素に overflow: hidden が指定されていないか確認しましょう。

原因2: 包含ブロックによる固定範囲の制限 #

以下の例では、右上に配置したボタンをスクロールに対して固定しようとしていますが、position: sticky が機能していません。

<article>
  <header>
    <button type="button">固定したいボタン</button>
  </header>

  記事の本文部分。
</article>
article {
  position: relative;
  margin-inline: auto;
  max-width: 400px;
  min-height: 400px;
  border: 1px solid black;
  padding: 8px;
}

header {
  /* 右側に配置する */
  position: absolute;
  top: 8px;
  right: 8px;
}

button {
  /* ボタンを固定表示しようとしている (機能しない) */
  position: sticky;
  top: 8px;
}

position: sticky の要素は包含ブロックの範囲を超えて固定されることはありません。この例では、<button> の包含ブロックが <header> の領域に限定されているため、期待通りの固定表示ができません。

包含ブロックとは #

包含ブロック(Containing Block)とは、要素の大きさや位置を決定する際の基準となる領域のことです。多くの場合、親要素のコンテンツ領域が包含ブロックとなります。

例えば、width: 50% は親要素の幅を基準に計算され、position: absolute は最も近い position: static 以外の要素を基準に配置されます。これらの基準となる領域が包含ブロックです。

position の値を親子で入れ替える #

この問題を解決するには、position: stickyposition: absolute を親子で入れ替えます。つまり、親要素を固定し、子要素をその中で位置指定する構造に変更します。

<article>
  <header>
    <button type="button">固定したいボタン</button>
  </header>

  記事の本文部分。
</article>
article {
  position: relative;
  margin-inline: auto;
  max-width: 400px;
  min-height: 400px;
  border: 1px solid black;
  padding: 8px;
}

header {
  /* ヘッダーの方を固定する */
  position: sticky;
  top: 8px;
}

button {
  /* 固定したヘッダーの中でボタンを右側に配置する */
  position: absolute;
  top: 0;
  right: 0;
}

この構造では、<header> の包含ブロックが <article> となり、スクロールに対して固定されます。<button><header> を基準に絶対配置されるため、結果として両要素が一体となって固定表示されます。

結論 #

position: sticky が動作しない場合は、主に2つの原因が考えられます。

1つ目はスクロールコンテナーの問題です。祖先要素の overflow: hidden が意図しないスクロールコンテナーを生成し、固定の基準を変えてしまいます。overflow: clip に置き換えることで、はみ出しを防ぎつつ期待通りの固定動作を実現できます。

2つ目は包含ブロックの問題です。position: sticky の要素は包含ブロックの範囲内でのみ固定されるという制約があります。親子要素の position 値を適切に組み合わせることで、より柔軟な固定表示を実現できます。

これらの仕様を理解しておけば、より柔軟なレイアウト設計が可能になります。