CSS設計

position: stickyが効かない理由と仕組み

読了 6分
CSS設計

position: stickyが効かない理由と仕組み

position: sticky を指定したのにヘッダーが貼り付かなくて、しばらく「なんで効かないの…」と悩んだので、そのときのメモです。

調べてみると、効かない原因はだいたい決まったパターンに収まるようです。まず仕組みを理解して、つまずきを順に潰していきます。

stickyは「relativeとfixedのあいだ」

sticky 要素は、スクロール位置に応じて2つの状態を行き来します。

  • 指定した閾値(top など)に達するまでは、ふつうの relative のように流れに沿って動く
  • 閾値に達すると、その位置で fixed のように固定される

つまり「ある位置まではスクロールに合わせて動いて、そこに来たら貼り付く」という二面性が sticky の本質です。この性質を使うと、セクション見出しをスクロール中に上端へ貼り付ける、といったUIが作れます。

.section-heading {
  position: sticky;
  top: 0;
}

つまずき1:閾値(top等)を指定していない

いちばん多い原因がこれです。自分もこれでした。stickyどこで貼り付くかの基準 がないと固定状態になれません。top / bottom / left / right のいずれかを必ず指定します。

/* これだけでは何も起きない */
.el {
  position: sticky;
}

/* 上端から0pxで貼り付く */
.el {
  position: sticky;
  top: 0;
}

top: 0 は「ビューポート(またはスクロールコンテナ)の上端に来たら固定する」という意味です。少し余白を空けたいなら top: 1rem のようにします。

つまずき2:祖先にoverflowが効いている

sticky は「最も近い スクロール可能な祖先 」を基準に動きます。ここが落とし穴で、親や祖先のどこかに overflow: hidden(や auto / scroll)が付いていると、その要素がスクロールコンテナとみなされます。

その祖先自体がスクロールしないサイズだと、sticky は貼り付く機会を得られず、固定されないように見えます。overflow: hidden は横はみ出し対策などで何気なく付けがちなので、効かないときは祖先をさかのぼって確認してください。自分はこれで半日溶かしました。まぁしかたないですね。

/* この overflow がページ全体のstickyを壊すことがある */
.wrapper {
  overflow-x: hidden;
}

回避策としては、はみ出し対策を別の方法(max-widthclip など)に置き換える、sticky の基準にしたい範囲だけにスクロールコンテナを限定する、といった手があります。

つまずき3:親の高さが足りない

sticky 要素は 親要素の領域の中でだけ 貼り付きます。親の高さが要素とほぼ同じだと、スクロールしてもすぐ親の終端に達してしまい、貼り付いている時間がほぼゼロになります。

「サイドバーをスクロール中ずっと追従させたい」なら、その親が十分に高い(隣のコンテンツと同じ高さがある)必要があります。意図どおり追従しないときは、親の高さを疑ってみてください。

よくある原因の早見表

困ったときはこの表を上から見ると早いです。

症状主な原因
まったく固定されないtop 等の閾値が未指定
一部のページだけ効かない祖先に overflow: hidden
すぐ離れてしまう親の高さが要素と同程度
横スクロールで貼り付かない閾値を left/right で指定していない

動作を確かめる

ブラウザのDevToolsで要素を選んで、position の計算値と、祖先に overflow が無いかを確認すると原因を絞り込めます。Chromeでは sticky 要素にバッジが表示されて、貼り付き状態を見分けやすくなっています。これは便利です。

まとめ

  • sticky は閾値に達するまで relative、達したら fixed のように振る舞う
  • top 等の閾値を必ず指定する(無いと固定できない)
  • 祖先の overflow がスクロールコンテナを変えてしまうことに注意
  • 追従させたいなら親に十分な高さを持たせる

仕様の細部はMDNのpositionにまとまっています。原因はだいたいこの3パターンなので、上から順に確認すれば多くは解決すると思います。以上です。