この記事は最終更新日から 2 年以上が経過しており、内容が古くなっている可能性があります。
CSSプロパティのposition: stickyを使用したときに上手く動作せず嵌ったので、その解決法についてまとめた。
今回嵌ったposition: sticky
当サイトでは、ヘッダー部分でposition: stickyを使用していた。ヘッダー内には、ボタンクリックでメニューが開閉するハンバーガーメニューを置いている。
構造は以下のとおり(※一部簡略化)。
<body>
<header role="banner" class="header">
<div class="header-inner">
...
<nav role="navigation" class="nav">
<button>
<span class="bar"></span>
<span class="sr-only">MENU</span>
</button>
<ul class="list">
<li class="list-item">HOME</li>
<li class="list-item">Post</li>
<li class="list-item">About</li>
</ul>
</nav>
</div>
</header>
<main class="main">
...
</main>
<footer class="footer">
...
</footer>
</body>
ヘッダー(.header)にposition: stickyを設定。初期状態ではメニュー(.list)を隠しておき、ボタンクリックで右側からスライドして表示されるという仕組みになっている。
.header {
height: 40px;
position: sticky;
top: 0;
}
.list {
position: fixed;
z-index: 100;
inset: 0 -100% 0 100%;
transition: transform 0.4s;
display: grid;
gap: 20px;
place-content: center;
text-align: center;
min-height: 100dvh;
}
inset: 0 -100% 0 100%で、メニューを画面外(右)に追いやっている。
これでヘッダーはスクロールすると画面上に追随するようになり、メニューはボタンクリックで右からスライドしてくるようになった…と思われた。
ここで2つの問題が発生した。
画面が横スクロールする

ウィンドウ下を見ると横スクロールバーが表示されていた。メニューが画面外(右)に隠れているのかと思いきや、コンテンツ横に幅100%状態で並んで画面幅が計200%になってしまっている。
スマホ版でposition: stickyが効かない
PC版は追随してくれるが、スマホ版は一瞬固定されてもすぐに剥がれてコンテンツと一緒にスクロールされる。
ちなみに、メインコンテンツの高さを調整するためにbodyにdisplay:gridを指定しているが、bodyに対してdisplay:gridやdisplay:flexを適用することは可能で、今回の不具合とは無関係である。
解決法
あくまでposition: stickyで指定したい場合
position: stickyが適用されないのは、そもそもスティッキーコンテナが存在していないことが原因であった。
私はbodyをdivやsectionのように親として扱えると思っていたが、bodyはスティッキーコンテナにすることはできない。スマホ版で、スティッキーアイテムが一瞬固定されてもすぐ剥がれてしまうのはこのためだと思われる(PC版で効いていた理由は不明だが)。
position:stickyを使う場合は、body配下にページ全体を囲むdivを設けてこれをスティッキーコンテナにする。
<body>
<div class="wrapper">
<header role="banner" class="header">
...
</header>
<main class="main">
...
</main>
<footer class="footer">
...
</footer>
</div>
</body>
横スクロール問題は、スティッキーコンテナ(.wrapper)にoverflow-x: clipをかけることで解決する。overflow: hiddenでも良さそうに思えるが、スティッキーアイテムの親・祖先要素にoverflow: hiddenを指定するとposition: stickyが効かなくなってしまう。
overflow: clipも使いたくない場合は、いっそ中のコンテンツをスティッキーアイテムから出した方がいいかもしれない。
position: fixedに変更する
スタイリングのためだけに余計なDOMを増やしたくない場合は、いっそstickyをやめて従来のfixedに書き換えるという手もある。
.header {
height: 40px;
position: fixed;
top: 0;
width: 100%;
}
.main {
margin-top: 40px;
}
fixedの場合は、右に追いやったメニューがスクロールで出てくる現象は発生しないので、親要素にoverflowプロパティを指定する必要はない。その代わり、.headerに幅を指定し、次の要素のmargin-topに.headerの高さ分を指定する必要がある。
position: stickyとposition: fixedの違い
なぜ、position: stickyの場合に画面外のコンテンツ分のスクロールが発生したのか。position: stickyとposition: fixedの違いについて少し調べてみた。
position: sticky
Sticky positioning, which visually shifts a box relative to its laid-out location in order to keep it visible when a scrollable ancestor would otherwise scroll it out of sight.
CSS Positioned Layout Module Level 3
「スティッキーポジショニングは、レイアウトされた位置から視覚的に相対ボックスを移動させ、スクロール可能な祖先要素がスクロールで見えなくなっても、ボックスを表示し続ける」
stickyは、relative同様にstaticに(親要素のボックス配置の文脈に従って)相対的に配置される。
position: fixed
Fixed positioning, which absolutely positions the box and affixes it to the viewport or page frame so that it is always visible.
CSS Positioned Layout Module Level 3
「フィックスドポジショニングは、ボックスが常に見えるように、ビューポートまたはページフレームに対して絶対的に配置、貼り付けられる」
一方のfixedは、absoluteのようにフローから外れて絶対配置される。フローから外れるということはつまり、fixed指定されたボックスの幅と高さは消失する。
stickyはフローからは外れないので、.headerの高さ・幅は消失しない。そのため、初期状態で画面右に追いやったはずのリストが画面スクロールで出てくるといった現象が発生したようだ。
まとめ
position: stickyが指定されたスティッキーアイテムはstaticに配置される。幅・高さは失われずその領域は保たれる。スティッキーアイテム内のコンテンツをposition: fixedで画面外に配置すると、スティッキーアイテムからはみ出す。position: stickyを使う場合は、body配下にスティッキーコンテナとなるdivを設ける。position: stickyではみ出したコンテンツを隠す場合は、overflow: hiddenではなくoverflow: clipを指定するか、コンテンツをスティッキーアイテム外に配置する。- DOMを増やしたくない場合は、
position: fixedで指定する。
追記
あなたが教わってるそのCSSテクニックはもう古い | TAKLOG
上の記事「画面いっぱいのメインビジュアルを実装するなら高さの値に 100svb(100svh) を使いなさい」の項では、bodyをスティッキーコンテナにして.footer要素を最下部に固定する方法が紹介されており、実際に上記ブログで.header .footer の固定化が実現されている。
しかし、当ブログでは同じように設定して(したつもりで)も再現できない。恐らく何らかの条件が異なるからだと思われるが…(要検討)。