position: stickyが効かない!原因の見つけ方と解決方法を初心者向けに徹底解説
「position: stickyを指定したのに、ヘッダーがスクロールに追従しない…」「サイドバーを固定したいのに、普通にスクロールされてしまう…」
CSS学習中のあなたは、こんな悩みを抱えていませんか?実はposition: stickyが効かない原因のほとんどは、親要素のoverflowプロパティか、top/bottomの位置指定の漏れにあります。
position: stickyが効かない主な原因4つと、それぞれの解決方法stickyとfixedの違い・正しい使い分け- 追従ヘッダーとサイドバー固定の実践コード
- 「それでも効かない」ときに使えるデバッグフローチャート
初心者の方にも分かるように、コード例とDevToolsでの確認手順を交えながら丁寧に解説していきます。読みながら手を動かせる構成にしていますので、ぜひ一緒に試してみてください。
早く知りたい人向け:position: stickyが効かない原因チェックリスト
まずは以下のチェックリストを上から順に確認してください。ほとんどのケースはこの4項目のどれかに該当します。
| チェック順 | 確認内容 | 対処法 |
|---|---|---|
| 1 | topやbottomの位置指定があるか |
top: 0;などを追加する |
| 2 | 親・先祖要素にoverflow: hidden / auto / scrollがないか |
該当のoverflowを削除、またはvisibleに変更 |
| 3 | 親要素に十分な高さがあるか(stickyが動く余白があるか) | 親要素の高さを確保する・構造を見直す |
| 4 | 親・先祖要素にtransform / perspective / filterがないか |
該当プロパティを削除または別要素に移動 |
それぞれの原因について、コード例付きでこの後くわしく解説していきます。
position: stickyとは?fixedとの違いを理解する
position: stickyは、スクロール位置に応じて「通常配置」と「固定配置」を自動で切り替えるCSSプロパティです。指定した閾値(たとえばtop: 0)に達するまでは通常のフローに従い、閾値を超えた時点でその位置に固定(スティッキー)されます。
Web制作では、追従ヘッダーやサイドバーの固定表示などでよく使われます。似たプロパティにposition: fixedがありますが、両者には明確な違いがあります。
stickyとfixedの違い
| 比較項目 | position: sticky | position: fixed |
|---|---|---|
| 基準 | 親要素のスクロール範囲内 | ビューポート(画面)全体 |
| 固定のタイミング | 指定位置に達したときだけ | 常に固定 |
| 通常フローへの影響 | 元の場所を占有したまま | フローから外れる(場所を占有しない) |
| 親要素の範囲を超えるか | 超えない(親の範囲内で固定) | 超える(常にビューポート基準) |
| 主な用途 | セクション内ヘッダー固定、サイドバー追従 | グローバルヘッダー、トップへ戻るボタン |
fixedはページ全体を通じて常に画面に固定されますが、stickyは親要素の範囲内でだけ固定されるのが最大の違いです。この「親要素の範囲内」という制約こそが、stickyが効かなくなる原因と深く関わっています。
stickyの基本的な使い方
position: stickyを使うには、最低限「position: sticky」と「top(またはbottom / left / right)」の2つを指定します。
- .sticky-header {
- position: sticky;
- top: 0;
- }
この2行がstickyの基本形です。top: 0は「ビューポートの上端に達したら固定する」という意味になります。シンプルに見えますが、stickyが効かなくなる原因は親要素や先祖要素の設定にあることが多く、自分の書いたコードだけを見ていても気づきにくいのが厄介なポイントです。
原因1:topやbottomなどの位置指定がない
position: stickyが効かない原因の中で、最も単純かつ見落としやすいのがこれです。stickyにはtop・bottom・left・rightのいずれかの指定が必須です。これがないと、ブラウザは「どの位置で固定すればいいのか」を判断できず、stickyとして機能しません。
問題のあるコード例
- /* NG:topが指定されていない */
- .header {
- position: sticky;
- background: #fff;
- }
改善後のコード
- /* OK:top: 0 を追加 */
- .header {
- position: sticky;
- top: 0;
- background: #fff;
- }
ヘッダーを画面上部に追従させたい場合はtop: 0;、サイドバーを下部に固定したい場合はbottom: 0;を指定します。top: 20px;のように値を変えれば、画面上端から20pxの位置で固定することも可能です。
原因2:親要素や先祖要素にoverflow: hidden / auto / scrollが指定されている
これがstickyが効かない原因の中で、最も多くの人がハマるパターンです。sticky要素の親、またはそれより上の先祖要素にoverflow: hidden・overflow: auto・overflow: scrollのいずれかが指定されていると、stickyは機能しなくなります。
ややこしいのは、直接の親要素だけでなく、さらに上の階層の先祖要素にoverflowが指定されている場合も影響を受ける点です。CSSリセットやフレームワークのデフォルトスタイルでoverflow: hiddenがbodyやラッパー要素に指定されていることも多く、原因に気づきにくいケースが少なくありません。
問題のあるコード例
- <div class="wrapper">
- <header class="sticky-header">ヘッダー</header>
- <main>コンテンツ...</main>
- </div>
- /* NG:親要素にoverflow: hiddenがある */
- .wrapper {
- overflow: hidden;
- }
- .sticky-header {
- position: sticky;
- top: 0;
- }
改善後のコード
- /* OK:overflowをvisible(初期値)に戻す */
- .wrapper {
- overflow: visible;
- }
- .sticky-header {
- position: sticky;
- top: 0;
- }
DevToolsで原因を特定する手順
overflowの問題は「どの先祖要素が原因なのか」を見つけるのが難しい部分です。以下の手順でChrome DevToolsを使って効率的に特定しましょう。
- sticky要素を右クリック →「検証(Inspect)」を選択します。
- Elementsパネルで、sticky要素が選択された状態を確認します。
- 右側の「Computed」タブを開き、Filterに「overflow」と入力します。
- sticky要素の親要素をクリックし、
overflowの値がvisible以外になっていないかを確認します。 - 問題がなければ、さらに一つ上の先祖要素を選択し、同じ確認を繰り返します。
overflow: hidden・auto・scrollが見つかったら、それが原因です。
注意:overflow-xやoverflow-yも同様に影響します。たとえば横スクロール防止のためにoverflow-x: hiddenだけを指定している場合でも、stickyが効かなくなるケースがあります。その場合はoverflow-x: clipへの変更を検討してください。clipはhiddenと同じようにはみ出しを非表示にしますが、stickyの動作には影響を与えません。
- /* overflow: hidden の代替として clip を使う */
- .wrapper {
- overflow-x: clip;
- }
原因3:親要素に十分な高さがない(stickyが動く余白がない)
position: stickyは、親要素の範囲内でのみ固定動作するという大前提があります。つまり、親要素の高さとsticky要素の高さがほぼ同じ場合、「固定しながらスクロールできる距離」がゼロになるため、stickyが効いていないように見えます。
これは特に、sticky要素だけが親要素の唯一の子要素であるときに起こりやすい問題です。
問題のあるコード例
- <!-- NG:sidebar-wrapの高さ ≒ sidebarの高さ -->
- <div class="sidebar-wrap">
- <aside class="sidebar">サイドバー</aside>
- </div>
- .sidebar {
- position: sticky;
- top: 20px;
- }
上記の場合、.sidebar-wrapは中身の.sidebarと同じ高さにしかならないため、stickyが固定されながら移動できる余白がありません。
改善後のコード
解決方法は、sticky要素の親がメインコンテンツと同じ高さを持つようにレイアウト構造を見直すことです。CSS Gridで横並びにすれば、サイドバー列はメインコンテンツ列と同じ高さになります。
- <div class="layout">
- <main class="main-content">長いコンテンツ...</main>
- <aside class="sidebar">サイドバー</aside>
- </div>
- .layout {
- display: grid;
- grid-template-columns: 1fr 300px;
- gap: 30px;
- }
- .sidebar {
- position: sticky;
- top: 20px;
- align-self: start;
- }
注意:CSS Gridを使ったレイアウトでは、align-self: startの指定が非常に重要です。Gridのデフォルトでは子要素がstretch(親と同じ高さに引き伸ばし)になるため、これをstartにしないとsticky要素も親と同じ高さになり、固定動作しません。Flexboxの場合も同様にalign-self: flex-startを指定してください。
原因4:親要素にtransform・perspective・filterが指定されている
あまり知られていない原因ですが、先祖要素にtransform・perspective・filterが指定されていると、stickyの基準となる包含ブロックが変わり、期待通りに動作しなくなることがあります。
これらのプロパティは新しい「包含ブロック(containing block)」を生成します。その結果、stickyの固定位置の基準がビューポートではなく、そのプロパティが指定された要素に変わってしまいます。この概念は、CSSのz-indexが効かない原因と解決方法で解説している「スタッキングコンテキスト」とも深く関わっています。
問題のあるコード例
- /* NG:先祖要素にtransformがある */
- .page-wrapper {
- transform: translateZ(0);
- }
- .sticky-header {
- position: sticky;
- top: 0;
- }
改善後のコード
- /* OK:transformを削除 */
- .page-wrapper {
- /* transform: translateZ(0); を削除 */
- }
- .sticky-header {
- position: sticky;
- top: 0;
- }
GPUアクセラレーションのためにtransform: translateZ(0)やwill-change: transformを指定しているケースは実務でも多いため、stickyが効かないときは先祖要素のこれらのプロパティも必ず確認しましょう。DevToolsのComputedタブで「transform」「filter」「perspective」をそれぞれ検索すると効率的に見つけられます。
実践:追従ヘッダーとサイドバー固定のコード例
ここまでの原因と対処法を踏まえて、実際によく使われる2つのパターンの実装コードを紹介します。そのままコピーして動作確認できますので、手を動かしながら試してみてください。
パターン1:追従ヘッダー
- <body>
- <header class="site-header">
- <nav>ナビゲーション</nav>
- </header>
- <main>
- <p>長いコンテンツがここに入ります...</p>
- </main>
- <footer>フッター</footer>
- </body>
- .site-header {
- position: sticky;
- top: 0;
- z-index: 100;
- background: #ffffff;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- padding: 15px 20px;
- }
z-indexを指定しないと、後続コンテンツが上に重なることがありますbackgroundを指定しないと、固定時にコンテンツが透けて見えます- 親要素(この場合は
body)にoverflow: hiddenがないことを確認しましょう
パターン2:サイドバー固定(CSS Grid使用)
メインコンテンツが長い場合に、サイドバーを画面内に固定表示するパターンです。CSS Gridとの組み合わせが実務では最もよく使われます。
- <div class="page-layout">
- <main class="content">
- <h1>記Copy 途中で止まってしまいたましたね はい、申し訳ありません。文字量が多く途中で切れてしまいました。途切れた箇所から続きを出力します。 (*4 続き ─ パターン2のHTML途中から再開) Copy事タイトル</h1>
- <p>本文が長く続きます...</p>
- <p>さらに続きます...</p>
- </main>
- <aside class="sidebar">
- <div class="sidebar-inner">
- <h2>カテゴリー</h2>
- <ul>
- <li>HTML</li>
- <li>CSS</li>
- <li>JavaScript</li>
- </ul>
- </div>
- </aside>
- </div>
- .page-layout {
- display: grid;
- grid-template-columns: 1fr 280px;
- gap: 40px;
- max-width: 1100px;
- margin: 0 auto;
- padding: 40px 20px;
- }
- .sidebar {
- position: sticky;
- top: 20px;
- align-self: start;
- }
このコードのポイントは3つあります。まず、display: gridでメインとサイドバーを横並びにすることで、サイドバーの親であるGridコンテナがメインコンテンツと同じ高さを自動で持ちます。次に、align-self: startでサイドバーが親の高さいっぱいに引き伸ばされるのを防ぎます。最後に、top: 20pxでビューポート上端から20pxの位置で固定されるようにしています。
「それでも効かない」ときのデバッグフローチャート
ここまでの原因をすべて確認しても解決しない場合は、以下のフローチャートに沿って順番にチェックしてみてください。DevToolsを開きながら進めると効率的です。
ステップ1:sticky要素自身を確認する
- DevToolsのElementsパネルでsticky要素を選択します。
- Computedタブで
positionの値がstickyになっているか確認します。他のCSSルールで上書きされていないかチェックしましょう。 top・bottom・left・rightのいずれかに値が入っているか確認します。
ここで問題が見つかればそれが原因です。見つからなければステップ2へ進みます。
ステップ2:親要素を1つずつたどってoverflowを確認する
- sticky要素の直接の親要素を選択します。
- Computedタブで「overflow」をフィルタ検索し、値が
visible以外になっていないか確認します。 - 問題がなければ、さらに1つ上の先祖要素に移動して同じ確認を繰り返します。
html要素まで確認して問題がなければステップ3へ進みます。
ステップ3:親要素の高さを確認する
- sticky要素の直接の親要素を選択します。
- Computedタブでその親要素の
heightを確認します。 - sticky要素自身の
heightと比較して、親の方が十分に大きいかを確認します。 - 親と子の高さがほぼ同じであれば、レイアウト構造の見直し(原因3を参照)が必要です。
ステップ4:transform / filter / perspectiveを確認する
- sticky要素の先祖要素を1つずつたどります。
- Computedタブで「transform」「filter」「perspective」をそれぞれ検索します。
none以外の値が見つかったら、それが原因の可能性があります。
- DevToolsのElementsパネルで要素を選択し、右側のStylesタブから直接CSSプロパティを無効化(チェックを外す)できます。怪しいプロパティを一時的にオフにして挙動が変わるか試すと、原因を素早く絞り込めます。
overflowはoverflow-xとoverflow-yの一括指定です。片方だけがhiddenになっているケースもあるため、両方を確認しましょう。
よくある質問
position: stickyとposition: fixedはどちらを使えばいいですか?
ページ全体で常に画面に固定したい要素(グローバルヘッダー、トップへ戻るボタンなど)にはfixedが適しています。一方、特定のセクション内だけで追従させたい要素(サイドバー、セクション見出しなど)にはstickyが適しています。stickyは通常のフローを維持するため、レイアウト崩れが起きにくいというメリットもあります。
overflow: hiddenを外せない場合はどうすればいいですか?
デザイン上overflow: hiddenが必要な場合は、代わりにoverflow: clipを使うことを検討してください。clipはhiddenと同じようにはみ出した内容を非表示にしますが、stickyの動作には影響を与えません。ただし、overflow: clipはスクロールバーを生成しないため、overflow: autoやoverflow: scrollの代替にはならない点に注意が必要です。
Flexboxレイアウトでもstickyは使えますか?
はい、使えます。ただしFlexboxでもalign-itemsのデフォルト値がstretchのため、sticky要素が親と同じ高さに引き伸ばされてしまうことがあります。sticky要素側にalign-self: flex-startを指定すれば解決します。考え方はCSS Gridの場合と同じです。
stickyを指定した要素にz-indexは必要ですか?
必須ではありませんが、追従ヘッダーなどでは指定することを推奨します。z-indexを指定しないと、後続のコンテンツ要素が上に重なって表示されるケースがあるためです。z-indexの仕組みや効かないときの対処法については、CSSのz-indexが効かない!原因と5つの解決方法の記事で詳しく解説しています。
JavaScriptを使わなくてもstickyヘッダーは実装できますか?
はい、position: stickyを使えばJavaScriptなしでCSS だけで追従ヘッダーを実装できます。かつてはJavaScriptでスクロール量を監視してposition: fixedを切り替える方法が主流でしたが、現在は主要ブラウザすべてがstickyに対応しているため、CSSのみの実装が推奨されます。
まとめ
position: stickyが効かない原因と解決方法を4つのパターンに分けて解説しました。
topやbottomの位置指定は必須。忘れるとstickyは機能しない- 親・先祖要素の
overflow: hidden / auto / scrollが最も多い原因。DevToolsで1つずつたどって確認する - 親要素に十分な高さがないとstickyが動く余白がない。Gridの場合は
align-self: startを忘れずに - 先祖要素の
transform・filter・perspectiveも原因になり得る - デバッグはフローチャートの順番で進めると最速で原因を特定できる
stickyが効かない問題は、原因さえ特定できれば修正は簡単です。この記事のチェックリストとデバッグフローチャートを活用して、スムーズに解決してください。
sticky実装時にはz-indexの問題に直面することも多いです。合わせてCSSのz-indexが効かない!原因と5つの解決方法を初心者向けに解説も読んでおくと、positionプロパティ全体の理解がぐっと深まります。




