position: stickyが効かない理由と仕組み
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-width や clip など)に置き換える、sticky の基準にしたい範囲だけにスクロールコンテナを限定する、といった手があります。
つまずき3:親の高さが足りない
sticky 要素は 親要素の領域の中でだけ 貼り付きます。親の高さが要素とほぼ同じだと、スクロールしてもすぐ親の終端に達してしまい、貼り付いている時間がほぼゼロになります。
「サイドバーをスクロール中ずっと追従させたい」なら、その親が十分に高い(隣のコンテンツと同じ高さがある)必要があります。意図どおり追従しないときは、親の高さを疑ってみてください。
よくある原因の早見表
困ったときはこの表を上から見ると早いです。
| 症状 | 主な原因 |
|---|---|
| まったく固定されない | top 等の閾値が未指定 |
| 一部のページだけ効かない | 祖先に overflow: hidden |
| すぐ離れてしまう | 親の高さが要素と同程度 |
| 横スクロールで貼り付かない | 閾値を left/right で指定していない |
動作を確かめる
ブラウザのDevToolsで要素を選んで、position の計算値と、祖先に overflow が無いかを確認すると原因を絞り込めます。Chromeでは sticky 要素にバッジが表示されて、貼り付き状態を見分けやすくなっています。これは便利です。
まとめ
stickyは閾値に達するまでrelative、達したらfixedのように振る舞うtop等の閾値を必ず指定する(無いと固定できない)- 祖先の
overflowがスクロールコンテナを変えてしまうことに注意 - 追従させたいなら親に十分な高さを持たせる
仕様の細部はMDNのpositionにまとまっています。原因はだいたいこの3パターンなので、上から順に確認すれば多くは解決すると思います。以上です。