2026.09.15 泊まれる場所4,223 立ち寄りスポット46,522 営業中を確認したキャンプ場1,005 note を読む

position:sticky が効かないときに見る場所|原因は、半年前に自分で書いた一行だった

目次を画面に残したくて二行書いたのに、そのまま上へ流れていった。
悪いのはCSSではなく、その要素が入っている箱のほうだった。

記事ページの横に目次を出して、スクロールしても画面に残るようにしたかった。
よその技術系のサイトでよく見るやつだ。

調べたら、CSS を二行足すだけでできると書いてある。
要素に position: stickytop: 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。
親より大きいまま置かれた画像。

犯人が分かれば直し方は短い。
僕の場合は 100vw100% に変えて終わりだった。
半年前の自分は、この五分をやりたくなかっただけだ。

構造の都合でどうしても外せない箱もある。
そのときは、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日時点の日本語版ページを確認したものです。

まる子パパ

まる子パパ

会社員。受託開発のエンジニアで、その前は寿司職人・長距離運転手・農業。 技術ブログは、キャンプの合間に踏んだバグと、AI・Web開発の運用の失敗を書いています。 このサイト自体が、開発に携わっているCMS「コンテナ」の稼働中の実例です。 このサイトについて

ほかにも書いています

note・スタンプ

※noteは運営者が個人で書いているものです。

技術ブログ一覧へ戻る