position:sticky が効かないときに見る場所|原因は、昔書いた overflow の一行であることが多い
目次を画面に残したくて二行書いても、そのまま上へ流れていくことがある。
悪いのはCSSではなく、その要素が入っている箱のほうだ。
記事ページの横に目次を出して、スクロールしても画面に残るようにしたかった。
よその技術系のサイトでよく見るやつだ。
調べたら、CSS を二行足すだけでできると書いてある。
要素に position: sticky と top: 0 を書く。
それだけ。
この二行を書いても、スクロールすると目次がそのまま上へ流れて消えていくことがある。
数字を変えても、クラス名を疑っても、別のブラウザで開いても直らない。
多くの場合、この二行が「何を見ているのか」を知らないことが原因になる。
sticky は、二つの相手を見ている
仕様の説明を、あとになってちゃんと読んだ。
MDN にはこう書いてある。
「要素は文書の通常のフローに従って配置され、直近のスクロールする祖先および包含ブロック(直近のブロックレベル祖先、表関連要素を含む)に対して top, right, bottom, left の値に基づいて相対配置されます」。
相手が二つ出てくる。
ひとつは「直近のスクロールする祖先」。
もうひとつは「包含ブロック」、つまり親だ。
この二つは、それぞれ違う仕事をしている。
スクロールする祖先は、どこまで動いたら貼り付くかを決める。
親は、どこまで貼り付いていられるかを決める。
効かないときは、どちらかが自分の思っている相手ではない。
よくあるのは前者だ。
犯人は、昔書いた一行であることが多い
MDN には、もう一つ大事なことが書いてある。
「なお粘着要素は、直近の祖先がスクロールしない場合でも、『スクロールの仕組み』を持つ直近の祖先(overflow が hidden, scroll, auto, overlay として作成されたもの)に『粘着』します」。
ここで心当たりのある人は多いと思う。
overflow: hidden を付けた箱は、実際にはスクロールしない。
中身がはみ出ても、ただ隠れるだけだ。
それでもブラウザから見れば「スクロールの仕組みを持っている箱」なので、sticky の相手はそこになる。
スクロールしない箱に貼り付く、というのは、何も起きないのと同じだ。
目次はおとなしく親と一緒に上へ流れていく。
よくある光景だ。
では、その overflow: hidden はどこから来るのか。
スマホで開くと、横にほんの少しだけ動いてしまうページがある。
指で払うと右に数十ピクセルだけずれて、白い帯が見える。
画面の幅とページの幅がずれる話は前に書いたけれど、原因を探す前に、大枠へ overflow-x: hidden と書いて蓋をしてしまいがちだ。
その応急処置が、あとになって、まったく別の場所で効いてくる。
自分で書いた CSS でも、書いたことを忘れていると気づけない。
蓋を外すのではなく、はみ出しているものを探す
原因が分かったら、まず overflow-x: hidden を消してみる。
目次は動くようになる。
横スクロールも戻ってくる。
あの一行は原因を消していたわけではなく、見えなくしていただけだからだ。
だから今度ははみ出しているものを探す。
探すと、こういうものが出てくる。
幅を 100vw で指定したブロック。
縦のスクロールバーの幅が入らないので、その分だけ画面より広くなる。
端から端まで見せたい帯を作るときに、よくやる書き方だ。
負の margin で外へ引っぱり出した装飾。
折り返しの指定を持たない長い URL。
親より大きいまま置かれた画像。
犯人が分かれば直し方は短い。100vw を 100% に変えて終わり、ということも多い。
蓋をする前の五分を惜しまなければ、先に済んでいた話だ。
構造の都合でどうしても外せない箱もある。
そのときは、sticky にしたい要素をその箱の外側へ出すしかない。
CSS で粘るより、HTML の入れ子を一段変えたほうが早いことが多かった。
それでも動かないときの、あと二つ
overflow が無罪だったのに動かない、という相談も見かける。
ありがちなのはこの二つだ。
ひとつ。top も bottom も書いていない。
MDN には、はっきり書いてある。
「要素を粘着させる必要がある軸に対して、少なくとも 1 つのインセット(top, inset-block-start, right, inset-inline-end など)を auto 以外の値に設定する必要があります」。
「軸の inset プロパティが両方とも auto に設定されている場合、その軸では sticky 値は relative として動作します」。
エラーは出ない。
怒られないまま、静かに relative として振る舞う。
これがいちばん気づきにくい形だと思う。
ふたつ。親に、動ける余地がない。
sticky が貼り付いていられるのは親の中だけだ。
親の下端が来たら、そこで一緒に去っていく。
だから親の高さが要素とほとんど同じなら、貼り付いた瞬間に終わりが来る。
目次と本文を横に二列で並べるとき、これが起きやすい。
flex や grid で列を作ると、子は既定で親と同じ高さまで引き伸ばされる。
目次の入っている列が本文と同じ高さになり、その中の目次も引き伸ばされて、動く隙間が消える。
目次の側に align-self: start を一行足すと直る。
引き伸ばすのをやめさせると、上に余白ができて、そこを sticky が使えるようになる。
見る順番
同じところで止まらないように、順番を決めておくといい。
まず、top か bottom を書いたかを見る。
次に、開発者ツールで親を上へたどって、overflow の値を全部見る。
hidden も scroll も auto も、ぜんぶ同じ扱いになるので、hidden だからと油断しない。
それから、sticky にしたい要素の親の高さを見る。
要素とぴったり同じなら、そこが原因だ。
動いたあとの話も一つだけ。
MDN には、sticky や fixed の中身をスクロールすると描き直しの負担が増える、という注意がある。
対処として will-change: transform を足して独立した層で描かせる方法が挙げられている。
目次一つでは差が出にくいけれど、重い演出と一緒に使うときは覚えておきたい。
この二行が効かない理由は、たいてい二行の外にある。
自分が過去に書いた一行だったり、親の高さだったり、書き忘れた top: 0 だったりする。
CSS を疑うより先に、その要素がどんな箱に入っているかを見にいったほうが早い。
出典
※ 引用は2026年9月5日時点の日本語版ページを確認したものです。
ほかにも書いています
受託・note・スタンプこういう仕組みを作る相談を受けています
毎日の更新・データ集め・集計の自動化/3万円〜収益がいくらだったか、どの施策が外れたか
note / 個人の視点で掘り下げていますスタンプラリーに挑戦する
行った場所は、現地チェックインで記録に残せます※受託の相談とnoteは、運営者が個人で受けている・書いているものです。
