【CSS】stickyの使い方:fixedやabsoluteとの違い

css-sticky
ポートフォリオ向けサーバー
HTML/CSSスキルが活きる!
学んだ知識を活かして、
自分だけのブログを始めませんか?

WordPressブログなら、あなたのコーディング知識でデザインのカスタマイズが自由自在。
ブログデビューに最適な初心者向けサーバー環境を徹底比較しました。

CSSのposition: sticky;は、JavaScript不要でスクロールに応じた要素の追従・固定を実現するプロパティです。

本記事では、ヘッダーやサイドバー等の実装方法、テーブルやGridでの応用、「なぜか効かない」時の原因と解決策までを解説します。

また、CSSに関するカテゴリーページから学びたい内容を決めたい人は、以下のCSSページをご確認ください。

HTML/CSS学習者の方へ
「学んだコード、自分のブログで試してみませんか?」
多くのブロガーがデザインでつまずく中、コードが読めるあなたは圧倒的に有利です。
自由自在なサイト構築の第一歩となる、初心者向けサーバーを分かりやすく比較しました。

\ スキルを活かして情報発信 / ブログ向けレンタルサーバー4選を読む▶︎
目次

position: stickyとは:使い方とfixedとの違い

Webサイトを閲覧している際、下にスクロールしても追従してくる見出しや画面の端にくっついて離れないサイドバーを見たことはありませんか?

そういった「スクロールに連動した固定要素」をJavaScriptを使わずにCSSで実現するプロパティがposition: sticky;です。

ここでは、stickyプロパティの使い方、「fixedとの違い」や「効かない対策」を解説します。

position: stickyとは:使い方とfixedとの違い
  • スクロールに合わせて指定位置で固定する仕組み
  • 絶対固定の「fixed」や「absolute」との違い

スクロールに合わせて指定位置で固定する仕組み

position: sticky;は、一言で表すと「最初は通常通りスクロールされるが、指定した位置(閾値)に到達した瞬間に画面に固定される」というプロパティです。

例えばstickytopを指定すると、要素が画面の上端(top: 0)に到達するまでは普通にスクロールで上に移動しますが、上端に触れた瞬間に止まり画面に張り付いたままになります。

下端で固定したい場合はstickybottomを使用します。

追従要素を作る際は、「position: sticky;top(またはbottomleftright)は、2つで1つのセットとして記述すること」です。

⭕️ 注意:ページ全体ではなく「下のグレーの枠の中」をスクロールしてください!

⚠️ この枠の中を下にスクロールしてください ↓

まだまだスクロール ↓

もう少し下へ ↓

🌟 私は sticky 見出しです!(top: 0)

見出しが枠の上端でピタッと止まりましたね!

この文章を読んでいる間も、上の見出しはずっとついてきます。

JavaScriptを使わなくても、CSSだけでこんなに簡単に実装できます。

どんどんスクロールしても、親要素の終わりが来るまでは固定され続けます。

(さらに下へ…)

(さらに下へ…)

(さらに下へ…)

(さらに下へ…)

(さらに下へ…)

🏁 スクロール枠の終わり

/* ❌ stickyだけ書いて満足してしまう */
.title-fail {
  position: sticky; /* 🚨 topなどの座標がないと、スルーされてしまう! */
}

/* ⭕️ sticky と 座標(top等)は必ずセットで書く! */
.title-success {
  position: -webkit-sticky;
  position: sticky;
  top: 0; /* 💡 画面の上端から0pxの位置でピタッと止める */
}
HTMLコード表示
<div class="sticky-v2-wrapper">
  
  <p class="sticky-v2-caption">⭕️ 注意:ページ全体ではなく「下のグレーの枠の中」をスクロールしてください!</p>

  <div class="scroll-v2-container">
    
    <div class="content-v2-section">
      <p style="color:#dc3545; font-weight:bold;">⚠️ この枠の中を下にスクロールしてください ↓</p>
      <p>まだまだスクロール ↓</p>
      <p>もう少し下へ ↓</p>
    </div>

    <div class="sticky-v2-heading">
      🌟 私は sticky 見出しです!(top: 0)
    </div>

    <div class="content-v2-section is-v2-long">
      <p>見出しが枠の上端でピタッと止まりましたね!</p>
      <p>この文章を読んでいる間も、上の見出しはずっとついてきます。</p>
      <p>JavaScriptを使わなくても、CSSだけでこんなに簡単に実装できます。</p>
      <p>どんどんスクロールしても、親要素の終わりが来るまでは固定され続けます。</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
      <p>🏁 スクロール枠の終わり</p>
    </div>

  </div>

  <div class="sticky-v2-code-area">
    <span class="hl-comment">/* ❌ stickyだけ書いて満足してしまう */</span><br>
    <span class="hl-blue">.title-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span> <span class="hl-comment">/* 🚨 topなどの座標がないと、スルーされてしまう! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ sticky と 座標(top等)は必ずセットで書く! */</span><br>
    <span class="hl-blue">.title-success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">-webkit-sticky;</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 💡 画面の上端から0pxの位置でピタッと止める */</span><br>
    }
  </div>

</div>
CSSコード表示
.sticky-v2-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.sticky-v2-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #dc3545;
  text-align: center;
}

/* 💡 デモ用のスクロールエリア(親要素) */
.scroll-v2-container {
  height: 350px !important; /* 💡 高さを固定してスクロールバーを出す */
  overflow-y: auto !important; /* 💡 縦方向のスクロールを許可 */
  background-color: #e9ecef !important;
  border: 3px solid #adb5bd !important;
  border-radius: 8px !important;
  padding: 20px !important;
  margin-bottom: 20px !important;
  position: relative !important;
  display: block !important;
}

/* 中のコンテンツ */
.content-v2-section {
  padding: 20px !important;
  background-color: #fff !important;
  border-radius: 6px !important;
  margin-bottom: 20px !important;
  color: #333 !important;
  font-size: 15px !important;
  line-height: 1.8 !important;
  border: 1px dashed #ccc !important;
}
.content-v2-section.is-v2-long {
  height: 800px !important; /* スクロールさせるためにわざと極端に長くする */
}

/* =⭕️ 正解:stickyを使った見出し= */
.sticky-v2-heading {
  /* 💡 stickyの必須プロパティ(!importantで強制適用) */
  position: -webkit-sticky !important;
  position: sticky !important; 
  top: 0 !important; /* 💡 上端に触れたら固定 */
  z-index: 100 !important; /* 他の要素より手前に表示させる */
  
  /* 装飾 */
  background-color: #0d6efd !important;
  color: #fff !important;
  padding: 15px !important;
  margin: 0 0 20px 0 !important;
  border-radius: 6px !important;
  font-size: 18px !important;
  font-weight: bold !important;
  text-align: center !important;
  box-shadow: 0 4px 10px rgba(0,0,0,0.3) !important;
  display: block !important;
}

/* =コード解説エリア= */
.sticky-v2-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

.hl-blue { color: #61afef; font-weight: bold; }
.hl-red { color: #e06c75; font-weight: bold; }
.hl-green { color: #98c379; font-weight: bold; }
.hl-comment { color: #6c757d; font-style: italic; }

様々な要素で利用するpositionの使い方を詳しく知りたい人は「【CSS】positionの使い方とabsolute・fixed・relativeの使い分け」を一読ください。

絶対固定の「fixed」や「absolute」との違い

stickyを学ぶ際、fixedabsoluteといった他の配置プロパティとの違いを理解する必要があります。

  • fixed(絶対固定)
    画面(ブラウザのウィンドウ)を基準にして固定されます。
    一度固定されると、ページの一番下までずっと画面の同じ位置に居座り続けます。
  • absolute(絶対配置)
    親要素(relativeなど)を基準にして配置されます。
    スクロールすると一緒に流れて消えてしまいます。
  • sticky(粘着固定)
    スクロールすると固定されるのはfixedに似ていますが、決定的な違いは 「直近の親要素の範囲内でしか固定されない」 という点です。

実務では、「ヘッダーのように全ページで常に追従させたいものはfixedを使い、サイドバーや目次のように『特定のエリアが終わったら一緒にスクロールして消えてほしい』ものにはstickyを使うこと」です。

効かない時は「親の高さ」と「親のoverflow」を疑うことです。

⭕️ stickyは「親要素の終わり」で固定が解除され、一緒に流れていく!

🧲 fixed (ずっと固定)

📦 stickyの親要素(ここが終わるまで固定)

🍯 sticky (親の枠内だけ)

↓ 親要素のスクロールエリア ↓

↓ 親要素のスクロールエリア ↓

↓ 親要素のスクロールエリア ↓

↓ 親要素のスクロールエリア ↓

↓ 親要素のスクロールエリア ↓

⚠️ ここで親要素が終わり!stickyも押し出される!

ここは親要素の外側です。

fixedはずっとついてきますが、stickyはもういません。

(さらに下へ…)

(さらに下へ…)

(さらに下へ…)

/* 💡 fixed:親要素に関係なく、画面(ウィンドウ)にずっと固定 */
.item-fixed {
  position: fixed;
  top: 20px;
}

/* 💡 sticky:親要素の範囲内だけで固定。親が終わると一緒に消える */
.item-sticky {
  position: sticky;
  top: 20px;
}
HTMLコード表示
<div class="sticky-vs-wrapper">
  
  <p class="sticky-vs-caption">⭕️ stickyは「親要素の終わり」で固定が解除され、一緒に流れていく!</p>

  <div class="scroll-container-vs">
    
    <div class="vs-box is-fixed">
      🧲 fixed (ずっと固定)
    </div>

    <div class="sticky-parent-box">
      <p class="parent-label">📦 stickyの親要素(ここが終わるまで固定)</p>
      
      <div class="vs-box is-sticky">
        🍯 sticky (親の枠内だけ)
      </div>
      
      <p class="filler-text">↓ 親要素のスクロールエリア ↓</p>
      <p class="filler-text">↓ 親要素のスクロールエリア ↓</p>
      <p class="filler-text">↓ 親要素のスクロールエリア ↓</p>
      <p class="filler-text">↓ 親要素のスクロールエリア ↓</p>
      <p class="filler-text">↓ 親要素のスクロールエリア ↓</p>
      <p class="filler-text">⚠️ ここで親要素が終わり!stickyも押し出される!</p>
    </div>

    <div class="outside-box">
      <p>ここは親要素の外側です。</p>
      <p>fixedはずっとついてきますが、stickyはもういません。</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
      <p>(さらに下へ...)</p>
    </div>

  </div>

  <div class="sticky-vs-code-area">
    <span class="hl-comment">/* 💡 fixed:親要素に関係なく、画面(ウィンドウ)にずっと固定 */</span><br>
    <span class="hl-blue">.item-fixed</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">fixed;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">20px;</span><br>
    }<br><br>

    <span class="hl-comment">/* 💡 sticky:親要素の範囲内だけで固定。親が終わると一緒に消える */</span><br>
    <span class="hl-blue">.item-sticky</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">20px;</span><br>
    }
  </div>

</div>
CSSコード表示
.sticky-vs-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
  position: relative;
}

.sticky-vs-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

/* スクロールエリア(擬似的なブラウザ画面) */
.scroll-container-vs {
  height: 400px;
  overflow-y: scroll;
  background-color: #fff;
  border: 4px solid #333;
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 20px;
  position: relative; /* fixedの擬似的な基準にするため */
}

/* 共通の箱 */
.vs-box {
  width: 140px;
  padding: 15px;
  border-radius: 6px;
  font-weight: bold;
  color: #fff;
  text-align: center;
  box-shadow: 0 4px 6px rgba(0,0,0,0.2);
  z-index: 10;
}

/* =💡 fixed要素(画面に対する固定をシミュレート)= */
.is-fixed {
  position: absolute; /* ※実際の仕様ではfixedですが、デモの枠内に収める都合上absoluteでシミュレートしてスクロールイベントで動かします(通常はposition: fixed;と書きます) */
  top: 20px;
  left: 20px;
  background-color: #dc3545;
}
/* ※デモ用のハック:枠内fixedシミュレート */
.scroll-container-vs .is-fixed {
  position: sticky;
  top: 20px;
}

/* stickyの親要素 */
.sticky-parent-box {
  background-color: #e2e3e5;
  border: 2px dashed #adb5bd;
  border-radius: 8px;
  padding: 20px;
  margin-left: 160px; /* fixedを避ける */
  margin-bottom: 20px;
  min-height: 500px; /* 💡 stickyが動くための高さをしっかり確保! */
}

.parent-label {
  font-weight: bold;
  color: #495057;
  margin-top: 0;
}

.filler-text {
  color: #6c757d;
  margin: 40px 0;
}

/* =💡 sticky要素(親の中でだけ固定)= */
.is-sticky {
  position: -webkit-sticky;
  position: sticky;
  top: 20px; /* 上から20pxで張り付く */
  background-color: #0d6efd;
  margin-bottom: 30px;
}

/* 親の外側エリア */
.outside-box {
  background-color: #fff3cd;
  padding: 20px;
  border-radius: 8px;
  margin-left: 160px;
  height: 400px;
  color: #856404;
}

/* =コード解説エリア= */
.sticky-vs-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

overflowの使い方を詳しく知りたい人は「【CSS】overflowの使い方とhiddenやscrollの使い分け」を一読ください。

ヘッダー・フッター・サイドバーの固定

Web制作の現場において、stickyが活躍するのは「ヘッダー」「フッター」「サイドバー(目次など)」というWebサイトの骨格となる3大パーツです。

ここでは、各パーツの実装方法と実務で直面する「パーツ特有の対策」を解説します。

ヘッダー・フッター・サイドバーの固定
  • ヘッダーやナビゲーションを上部に追従させる
  • フッターを下部に固定する
  • サイドバーや目次を画面の横に固定する

ヘッダーやナビゲーションを上部に追従させる

Webサイトを下にスクロールしても、常に画面上部にメニューバーが張り付いているデザインは一般的です。

ヘッダーを上部固定する際は、「stickyを指定するヘッダーには、z-index: 100;などの『十分に高い重なり順』をセットで指定すること」です。

これを忘れるとレイアウト崩れを起こします。

⭕️ ヘッダーには必ず「高い z-index」をセットで指定しろ!

❌ 罠(z-indexなし)
下のコンテンツ(画像等)が上に被さる!

ヒーロー画像
メニュー (z-indexなし)
画像や要素が
ヘッダーの上に
被さってしまう!
(スクロール用)

⭕️ 成功(z-indexあり)
ヘッダーが常に一番上に表示される!

ヒーロー画像
メニュー (z-indexあり)
ヘッダーの下に
綺麗に潜り込む!
(スクロール用)
/* ❌ stickyとtopしか書かない */
.header-fail {
  position: sticky;
  top: 0; /* 🚨 これだけだと、他の要素の下敷きになる危険性が高い! */
}

/* ⭕️ ヘッダーなら必ず z-index で最前面に出す! */
.header-success {
  position: sticky;
  top: 0;
  z-index: 100; /* 💡 これで絶対に下敷きにならない! */
}
HTMLコード表示
<div class="st-head-wrapper">
  <p class="st-head-caption">⭕️ ヘッダーには必ず「高い z-index」をセットで指定しろ!</p>

  <div class="st-head-demo-area">
    
    <div class="st-head-item">
      <p class="st-head-label" style="color:#dc3545;">❌ 罠(z-indexなし)<br><small>下のコンテンツ(画像等)が上に被さる!</small></p>
      
      <div class="scroll-head-box">
        <div class="dummy-hero">ヒーロー画像</div>
        
        <div class="st-navbar is-trap-nav">メニュー (z-indexなし)</div>
        
        <div class="dummy-content is-relative-content">
          画像や要素が<br>ヘッダーの上に<br>被さってしまう!
        </div>
        <div class="dummy-content" style="height: 300px;">(スクロール用)</div>
      </div>
    </div>

    <div class="st-head-item">
      <p class="st-head-label" style="color:#0d6efd;">⭕️ 成功(z-indexあり)<br><small>ヘッダーが常に一番上に表示される!</small></p>
      
      <div class="scroll-head-box">
        <div class="dummy-hero">ヒーロー画像</div>
        
        <div class="st-navbar is-success-nav">メニュー (z-indexあり)</div>
        
        <div class="dummy-content is-relative-content">
          ヘッダーの下に<br>綺麗に潜り込む!
        </div>
        <div class="dummy-content" style="height: 300px;">(スクロール用)</div>
      </div>
    </div>

  </div>

  <div class="st-head-code-area">
    <span class="hl-comment">/* ❌ stickyとtopしか書かない */</span><br>
    <span class="hl-blue">.header-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 🚨 これだけだと、他の要素の下敷きになる危険性が高い! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ ヘッダーなら必ず z-index で最前面に出す! */</span><br>
    <span class="hl-blue">.header-success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
      <span class="hl-green">z-index:</span> <span class="hl-red">100;</span> <span class="hl-comment">/* 💡 これで絶対に下敷きにならない! */</span><br>
    }
  </div>
</div>
CSSコード表示
.st-head-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}
.st-head-caption {
  font-size: 14px; font-weight: bold; margin-bottom: 20px; color: #198754; text-align: center;
}
.st-head-demo-area {
  display: flex; flex-wrap: wrap; gap: 30px; justify-content: center;
  background-color: #e9ecef; padding: 30px 20px; border-radius: 8px; border: 1px dashed #adb5bd; margin-bottom: 20px;
}
.st-head-item { width: 250px; text-align: center; }
.st-head-label { font-size: 13px; font-weight: bold; margin-bottom: 10px; line-height: 1.5; color: #333; }

/* スクロールエリア */
.scroll-head-box {
  height: 250px !important;
  overflow-y: auto !important;
  background-color: #fff !important;
  border: 2px solid #adb5bd !important;
  border-radius: 6px !important;
  position: relative !important;
}

/* 擬似コンテンツ */
.dummy-hero { background-color: #e9ecef; padding: 30px; font-weight: bold; color: #6c757d; }
.dummy-content { padding: 20px; color: #333; border-bottom: 1px dashed #ccc; }

/* 画像などの相対配置要素をシミュレート */
.is-relative-content {
  position: relative !important;
  z-index: 10 !important;
  background-color: #ffeb3b !important; /* 目立つように黄色 */
  font-weight: bold !important;
}

/* 共通ナビゲーション */
.st-navbar {
  background-color: #0d6efd !important;
  color: #fff !important;
  padding: 15px !important;
  font-weight: bold !important;
  position: sticky !important;
  top: 0 !important; /* 上部固定 */
}

/* =❌ 罠:z-indexなし= */
.is-trap-nav {
  z-index: auto !important; /* 🚨 下の黄色いコンテンツに負ける */
}

/* =⭕️ 成功:z-indexあり= */
.is-success-nav {
  z-index: 100 !important; /* 💡 最前面を維持する */
  box-shadow: 0 4px 6px rgba(0,0,0,0.2) !important;
}

/* コードエリア */
.st-head-code-area { background-color: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; font-family: monospace; font-size: 13px; line-height: 1.6; border-left: 4px solid #0d6efd; overflow-x: auto; text-align: left; }
.hl-blue { color: #61afef; font-weight: bold; }
.hl-red { color: #e06c75; font-weight: bold; }
.hl-green { color: #98c379; font-weight: bold; }
.hl-comment { color: #6c757d; font-style: italic; }

重なり順を決めるz-indexの使い方を詳しく知りたい人は「【CSS】z-indexとは?使い方と効かない時の対処」を一読ください。

フッターを下部に固定する

スマホサイトで見かける「画面の下部に表示される購入ボタンやメニューバー」があります。

これもstickyを使うことで実装できます。

topが画面の上端を基準にするのに対し、bottom: 0;は「スクロール領域の下端」を基準にして固定されます。

コンテンツが長いページで、常にユーザーのアクションを促すフッターとして有効です。

画面下部に固定アクションバー(スマホ用メニューなど)を配置する際は、「大枠のfooter要素そのものにstickyをかけるのではなく、body全体を親要素とした『追従専用のバー』を独立して作り、position: sticky; bottom: 0;を指定すること」です。

※画面全体に対してずっと固定したいならposition: fixed; bottom: 0;の方が適している場合も多いので、親要素の終わりで消したいのか、ずっと出したいのかで使い分けましょう。

⭕️ 下部固定バーは「コンテンツの最後」に置くことで画面の底に張り付く!

下にスクロールしてください ↓

(長い記事コンテンツが続いています…)

画面に収まりきらないため、下部のアクションバーは「画面の底」に張り付いて追従してきます。

コンテンツの終端が近づいてきました。

🛒 カートに入れる (bottom: 0)
/* ⭕️ プロの鉄則:画面下部に張り付かせるなら bottom: 0 を使う */
.action-bar {
  position: -webkit-sticky;
  position: sticky;
  bottom: 0; /* 💡 画面の下端に触れている間はピタッと止める */
  z-index: 100; /* 💡 ヘッダー同様、z-indexは必須! */
}
HTMLコード表示
<div class="st-foot-v2-wrapper">
  
  <p class="st-foot-v2-caption">⭕️ 下部固定バーは「コンテンツの最後」に置くことで画面の底に張り付く!</p>

  <div class="scroll-foot-v2-box">
    
    <div class="dummy-main-v2-content">
      <p style="font-weight: bold; color: #dc3545;">下にスクロールしてください ↓</p>
      <div class="long-spacer">
        <p>(長い記事コンテンツが続いています...)</p>
        <p>画面に収まりきらないため、下部のアクションバーは「画面の底」に張り付いて追従してきます。</p>
        <p>↓</p>
        <p>↓</p>
        <p>↓</p>
        <p>コンテンツの終端が近づいてきました。</p>
      </div>
    </div>

    <div class="st-bottom-v2-bar">
      🛒 カートに入れる (bottom: 0)
    </div>
    
    <div class="normal-v2-footer">
      <h4>サイトの通常のフッター</h4>
      <p>ここでstickyバーは本来の位置(親要素の終わり)に到達するため、追従をやめて一緒に上に流れていきます。</p>
    </div>

  </div>

  <div class="st-foot-v2-code-area">
    <span class="hl-comment">/* ⭕️ 画面下部に張り付かせるなら bottom: 0 を使う */</span><br>
    <span class="hl-blue">.action-bar</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">-webkit-sticky;</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">bottom:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 💡 画面の下端に触れている間はピタッと止める */</span><br>
      <span class="hl-green">z-index:</span> <span class="hl-red">100;</span> <span class="hl-comment">/* 💡 ヘッダー同様、z-indexは必須! */</span><br>
    }
  </div>

</div>
CSSコード表示
.st-foot-v2-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.st-foot-v2-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

/* スクロールエリア */
.scroll-foot-v2-box {
  height: 400px !important; /* 💡 デモ用のスクロール枠 */
  overflow-y: auto !important;
  background-color: #e9ecef !important;
  border: 3px solid #adb5bd !important;
  border-radius: 8px !important;
  position: relative !important;
  display: block !important;
}

/* メインコンテンツ */
.dummy-main-v2-content {
  padding: 20px !important;
  color: #333 !important;
  background-color: #fff !important;
}

/* コンテンツを長引かせるためのスペーサー */
.long-spacer {
  height: 500px !important; /* 💡 枠の高さ(400px)より長くして、バーを画面外に押しやる */
  border-left: 3px dashed #ccc !important;
  padding-left: 15px !important;
  margin-top: 20px !important;
  color: #666 !important;
}

/* =⭕️ 正解:下部固定アクションバー= */
.st-bottom-v2-bar {
  /* stickyの必須設定 */
  position: -webkit-sticky !important;
  position: sticky !important;
  bottom: 0 !important; /* 💡 画面の下端に固定 */
  z-index: 100 !important;
  
  /* 装飾 */
  background-color: #dc3545 !important;
  color: #fff !important;
  padding: 20px !important;
  font-size: 18px !important;
  font-weight: bold !important;
  text-align: center !important;
  box-shadow: 0 -4px 10px rgba(0,0,0,0.2) !important;
  display: block !important;
}

/* 通常のフッター */
.normal-v2-footer {
  background-color: #343a40 !important;
  color: #adb5bd !important;
  padding: 60px 20px !important;
  text-align: center !important;
  display: block !important;
}

/* コードエリア */
.st-foot-v2-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
  margin-top: 20px;
}

.hl-blue {
  color: #61afef;
  font-weight: bold;
}

.hl-red {
  color: #e06c75;
  font-weight: bold;
}

.hl-green {
  color: #98c379;
  font-weight: bold;
}

.hl-comment {
  color: #6c757d;
  font-style: italic;
}

サイドバーや目次を画面の横に固定する

ブログ記事やメディアサイトで、左側の本文をスクロールしても右側のサイドバーにある「目次」や「広告」がずっとついてくるレイアウトです。

実現するには、display: flex;による2カラムレイアウトと組み合わせて使用されるのが王道パターンです。

Flexboxで作ったサイドバーをstickyで固定する際は、「サイドバーに対して、align-self: flex-start;を指定し、高さをコンテンツ本来のサイズに縮めること」です。

これで滑り降りる隙間が生まれ、追従し始めます。

⭕️ サイドバー固定が効かない?Flexboxの「stretch」を解除しろ!

❌ 罠(デフォルト/stretch)
サイドバーの高さが引き伸ばされ、固定されない

メインコンテンツ

↓ スクロール

⭕️ 成功(flex-startを指定)
高さが元に戻り、綺麗に追従し始める!

メインコンテンツ

↓ スクロール

/* ❌ Flexboxのデフォルト仕様(stretch)に気づかない */
.sidebar-fail {
  position: sticky;
  top: 20px; /* 🚨 親と同じ高さに引き伸ばされているため、動けない! */
}

/* ⭕️ align-self: flex-start で自分の高さを縮める! */
.sidebar-success {
  align-self: flex-start; /* 💡 高さが元に戻り、滑り降りる隙間ができる */
  position: sticky;
  top: 20px; /* 💡 これで完璧に追従する */
}
HTMLコード表示
<div class="st-side-wrapper">
  <p class="st-side-caption">⭕️ サイドバー固定が効かない?Flexboxの「stretch」を解除しろ!</p>

  <div class="st-side-demo-area">
    
    <div class="st-side-item">
      <p class="st-side-label" style="color:#dc3545;">❌ 罠(デフォルト/stretch)<br><small>サイドバーの高さが引き伸ばされ、固定されない</small></p>
      
      <div class="scroll-side-box">
        <div class="flex-container is-trap-flex">
          
          <div class="main-content">
            <p>メインコンテンツ</p>
            <p>↓ スクロール</p>
            <div style="height: 300px;"></div>
          </div>
          
          <div class="sidebar is-trap-sidebar">
            ❌ 追従しない!
          </div>

        </div>
      </div>
    </div>

    <div class="st-side-item">
      <p class="st-side-label" style="color:#0d6efd;">⭕️ 成功(flex-startを指定)<br><small>高さが元に戻り、綺麗に追従し始める!</small></p>
      
      <div class="scroll-side-box">
        <div class="flex-container is-success-flex">
          
          <div class="main-content">
            <p>メインコンテンツ</p>
            <p>↓ スクロール</p>
            <div style="height: 300px;"></div>
          </div>
          
          <div class="sidebar is-success-sidebar">
            ⭕️ 追従する!
          </div>

        </div>
      </div>
    </div>

  </div>

  <div class="st-side-code-area">
    <span class="hl-comment">/* ❌ Flexboxのデフォルト仕様(stretch)に気づかない */</span><br>
    <span class="hl-blue">.sidebar-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">20px;</span> <span class="hl-comment">/* 🚨 親と同じ高さに引き伸ばされているため、動けない! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ align-self: flex-start で自分の高さを縮める! */</span><br>
    <span class="hl-blue">.sidebar-success</span> {<br>
      <span class="hl-green">align-self:</span> <span class="hl-red">flex-start;</span> <span class="hl-comment">/* 💡 高さが元に戻り、滑り降りる隙間ができる */</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">20px;</span> <span class="hl-comment">/* 💡 これで完璧に追従する */</span><br>
    }
  </div>
</div>
CSSコード表示
.st-side-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}
.st-side-caption {
  font-size: 14px; font-weight: bold; margin-bottom: 20px; color: #198754; text-align: center;
}
.st-side-demo-area {
  display: flex; flex-wrap: wrap; gap: 30px; justify-content: center;
  background-color: #e9ecef; padding: 30px 20px; border-radius: 8px; border: 1px dashed #adb5bd; margin-bottom: 20px;
}
.st-side-item { width: 300px; text-align: center; }
.st-side-label { font-size: 13px; font-weight: bold; margin-bottom: 10px; line-height: 1.5; color: #333; }

/* スクロールエリア */
.scroll-side-box {
  height: 300px !important;
  overflow-y: auto !important;
  background-color: #fff !important;
  border: 2px solid #adb5bd !important;
  border-radius: 6px !important;
  text-align: left !important;
}

/* Flexコンテナ(2カラム) */
.flex-container {
  display: flex !important;
  gap: 15px !important;
  padding: 15px !important;
  /* デフォルトで align-items: stretch が効いている状態 */
}

/* メインコンテンツ(左側) */
.main-content {
  flex: 1 !important;
  background-color: #f1f3f5 !important;
  padding: 15px !important;
  border-radius: 6px !important;
  border: 1px dashed #ccc !important;
}

/* 共通のサイドバー設定 */
.sidebar {
  width: 100px !important;
  background-color: #198754 !important;
  color: #fff !important;
  padding: 15px 10px !important;
  border-radius: 6px !important;
  font-weight: bold !important;
  text-align: center !important;
  
  /* stickyの必須設定 */
  position: -webkit-sticky !important;
  position: sticky !important;
  top: 15px !important; 
}

/* =❌ 罠:align-selfなし(メインと同じ高さに引き伸ばされる)= */
.is-trap-sidebar {
  /* align-selfがないため、背景の緑色が下まで伸びきっており、動けない */
  background-color: #dc3545 !important;
}

/* =⭕️ 成功:align-self: flex-start を指定= */
.is-success-sidebar {
  align-self: flex-start !important; /* 💡 自分の本来の高さに縮むので、動ける! */
}

/* コードエリア */
.st-side-code-area { background-color: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; font-family: monospace; font-size: 13px; line-height: 1.6; border-left: 4px solid #0d6efd; overflow-x: auto; text-align: left; }

Flexboxの設定について詳しく知りたい人は「【CSS】flexの使い方:justify-content・align-items・gap」を一読ください。

テーブル(table)の見出しや列を固定する

データ量が多い表(table)をスマホや小さな画面で見る時、スクロールすると「この列のデータ、何の項目だっけ?」と見出しが消えて迷子になってしまう問題はWebデザインにおける課題でした。

しかし、現在はこの問題もposition: sticky;を使うことで解決できます。

ここでは、テーブル特有のstickyの当て方、横スクロール時の列固定、実務で直面する「枠線が消える・背景が透ける」というバグの対処法を解説します。

テーブル(table)の見出しや列を固定する
  • thead(ヘッダー行)を縦スクロールしても残す方法
  • 最初の列(横スクロール)や複数行・複数列の固定
  • 固定時にborder(枠線)が消える・透ける問題の対処法

thead(ヘッダー行)を縦スクロールしても残す方法

縦に長い表を下へスクロールした際、一番上の見出し行を常に画面上部に残す実装は、ユーザー体験(UX)を向上させます。

実装するには、ヘッダー要素に対してposition: sticky;top: 0;を指定するだけですが、テーブルタグ特有の「罠」が存在します。

テーブルのヘッダーを固定するには、「<tr>ではなく、その中にある見出しセル<th>要素に対して直接position: sticky; top: 0;を指定すること」です。

⭕️ ヘッダー固定は「tr」ではなく「th」に直接かけろ!

❌ 罠(trに指定)
ブラウザによっては無視され一緒に流れる

ID 名前 役職
1田中部長
2佐藤課長
3鈴木主任
4高橋一般
5伊藤一般

⭕️ 成功(thに指定)
どのブラウザでも綺麗に上部に固定される!

ID 名前 役職
1田中部長
2佐藤課長
3鈴木主任
4高橋一般
5伊藤一般
/* ❌ 行(tr)や thead を丸ごと固定しようとする */
tr.row-fail {
  position: sticky; /* 🚨 テーブルの構造上、うまく効かないことが多い */
  top: 0;
}

/* ⭕️ 個別のセル(th)に対して指定する! */
th.cell-success {
  position: -webkit-sticky;
  position: sticky;
  top: 0; /* 💡 枠の上端でピタッと固定 */
  z-index: 1; /* 💡 通常のセルより手前に出す */
}
HTMLコード表示
<div class="tbl-head-wrapper">
  
  <p class="tbl-head-caption">⭕️ ヘッダー固定は「tr」ではなく「th」に直接かけろ!</p>

  <div class="tbl-head-demo-area">
    
    <div class="tbl-head-item">
      <p class="tbl-head-label" style="color:#dc3545;">❌ 罠(trに指定)<br><small>ブラウザによっては無視され一緒に流れる</small></p>
      
      <div class="tbl-scroll-box">
        <table class="demo-table">
          <tr class="is-trap-tr">
            <th>ID</th>
            <th>名前</th>
            <th>役職</th>
          </tr>
          <tr><td>1</td><td>田中</td><td>部長</td></tr>
          <tr><td>2</td><td>佐藤</td><td>課長</td></tr>
          <tr><td>3</td><td>鈴木</td><td>主任</td></tr>
          <tr><td>4</td><td>高橋</td><td>一般</td></tr>
          <tr><td>5</td><td>伊藤</td><td>一般</td></tr>
        </table>
      </div>
    </div>

    <div class="tbl-head-item">
      <p class="tbl-head-label" style="color:#0d6efd;">⭕️ 成功(thに指定)<br><small>どのブラウザでも綺麗に上部に固定される!</small></p>
      
      <div class="tbl-scroll-box">
        <table class="demo-table">
          <tr>
            <th class="is-success-th">ID</th>
            <th class="is-success-th">名前</th>
            <th class="is-success-th">役職</th>
          </tr>
          <tr><td>1</td><td>田中</td><td>部長</td></tr>
          <tr><td>2</td><td>佐藤</td><td>課長</td></tr>
          <tr><td>3</td><td>鈴木</td><td>主任</td></tr>
          <tr><td>4</td><td>高橋</td><td>一般</td></tr>
          <tr><td>5</td><td>伊藤</td><td>一般</td></tr>
        </table>
      </div>
    </div>

  </div>

  <div class="tbl-head-code-area">
    <span class="hl-comment">/* ❌ 行(tr)や thead を丸ごと固定しようとする */</span><br>
    <span class="hl-blue">tr.row-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span> <span class="hl-comment">/* 🚨 テーブルの構造上、うまく効かないことが多い */</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ 個別のセル(th)に対して指定する! */</span><br>
    <span class="hl-blue">th.cell-success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">-webkit-sticky;</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 💡 枠の上端でピタッと固定 */</span><br>
      <span class="hl-green">z-index:</span> <span class="hl-red">1;</span> <span class="hl-comment">/* 💡 通常のセルより手前に出す */</span><br>
    }
  </div>

</div>
CSSコード表示
.tbl-head-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.tbl-head-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.tbl-head-demo-area {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.tbl-head-item {
  width: 250px;
  text-align: center;
}

.tbl-head-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* スクロールする枠 */
.tbl-scroll-box {
  height: 150px !important;
  overflow-y: auto !important;
  border: 2px solid #adb5bd !important;
  border-radius: 6px !important;
  background-color: #fff !important;
  position: relative !important;
}

/* テーブル共通設定 */
.demo-table {
  width: 100% !important;
  border-collapse: collapse !important;
  text-align: left !important;
  font-size: 14px !important;
}
.demo-table th, .demo-table td {
  padding: 10px !important;
  border-bottom: 1px solid #dee2e6 !important;
}
.demo-table th {
  background-color: #0d6efd !important;
  color: #fff !important;
}

/* =❌ 罠:trにsticky= */
.is-trap-tr {
  position: sticky !important;
  top: 0 !important;
  /* ※ブラウザによっては効かない。今回は「効かない場合」を想定したデモのため、他のthのstickyは外しています */
}

/* =⭕️ 成功:thにsticky= */
.is-success-th {
  position: -webkit-sticky !important;
  position: sticky !important;
  top: 0 !important; /* 💡 固定位置 */
  z-index: 1 !important;
  /* 境界線を維持するためのハックは後述 */
}

/* コードエリア */
.tbl-head-code-area { background-color: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; font-family: monospace; font-size: 13px; line-height: 1.6; border-left: 4px solid #0d6efd; overflow-x: auto; text-align: left; }
.hl-blue { color: #61afef; font-weight: bold; }
.hl-red { color: #e06c75; font-weight: bold; }
.hl-green { color: #98c379; font-weight: bold; }
.hl-comment { color: #6c757d; font-style: italic; }

テーブルの作り方を詳しく知りたい人は「【HTML】tableタグ(テーブル)の使い方:枠線・セル結合・レスポンシブ」を一読ください。

最初の列(横スクロール)や複数行・複数列の固定

スマホ対応において、横長の表を横スクロールさせるUIは必須です。

誰のデータか分かるように一番左の列を固定します。

また、複雑な表では2行目まで固定したい、2列目まで固定したいという要望も出ます。

複数列(または複数行)を固定するには、「2列目(2行目)以降は、手前の列(行)の『幅(高さ)』を計算し、その分だけ数値をズラしてleft: 100px;(またはtop: 50px;)のように指定すること」です。

⭕️ 複数列を固定する時は「手前の列の幅をガチガチに固定」して計算を合わせろ!

❌ 罠(すべて left: 0)
右にスクロールすると、1列目と2列目が重なって読めなくなる!

👉 右へスクロールして違いを確認してください 👉
ID 名前 年齢部署電話番号メールアドレス保有資格最終ログイン備考(ダミーテキストを長くして幅を広げています)
1 田中 28営業部090-XXXX-XXXXtanaka@example.comITパスポート2023/10/01あああああああああああああああああああああああああああ
2 佐藤 35開発部080-XXXX-XXXXsato@example.com基本情報技術者2023/10/05いいいいいいいいいいいいいいいいいいいいいいいいいいい

⭕️ 成功(幅を固定 & leftをズラす)
文字の重なりがなくなり、スクロールしても綺麗についてくる!

👉 右へスクロールして違いを確認してください 👉
ID 名前 年齢部署電話番号メールアドレス保有資格最終ログイン備考(ダミーテキストを長くして幅を広げています)
1 田中 28営業部090-XXXX-XXXXtanaka@example.comITパスポート2023/10/01あああああああああああああああああああああああああああ
2 佐藤 35開発部080-XXXX-XXXXsato@example.com基本情報技術者2023/10/05いいいいいいいいいいいいいいいいいいいいいいいいいいい
/* ❌ 固定したい列を全部 0 にしてしまう */
.col-1, .col-2 {
  position: sticky;
  left: 0; /* 🚨 スクロールすると2列目が1列目の上に覆いかぶさる! */
}

/* ⭕️ 1列目を60pxに固定したら、2列目は「left: 60px」にする! */
.col-1 {
  position: sticky;
  left: 0;
  width: 60px; /* 💡 幅を絶対に縮まないよう固定する */
  box-sizing: border-box;
}
.col-2 {
  position: sticky;
  left: 60px; /* 💡 1列目の幅の分だけ正確に右にズラす */
}
HTMLコード表示
<div class="tbl-col-v3-wrapper">
  
  <p class="tbl-col-v3-caption">⭕️ 複数列を固定する時は「手前の列の幅をガチガチに固定」して計算を合わせろ!</p>

  <div class="tbl-col-v3-demo-area">
    
    <div class="tbl-col-v3-item">
      <p class="tbl-col-v3-label" style="color:#dc3545;">❌ 罠(すべて left: 0)<br><small>右にスクロールすると、1列目と2列目が重なって読めなくなる!</small></p>
      
      <div class="tbl-x-scroll-v3-box">
        <div class="scroll-v3-hint">👉 右へスクロールして違いを確認してください 👉</div>
        <table class="demo-x-v3-table">
          <tr>
            <th class="trap-col-v3-1">ID</th>
            <th class="trap-col-v3-2">名前</th>
            <th>年齢</th><th>部署</th><th>電話番号</th><th>メールアドレス</th><th>保有資格</th><th>最終ログイン</th><th>備考(ダミーテキストを長くして幅を広げています)</th>
          </tr>
          <tr>
            <td class="trap-col-v3-1">1</td>
            <td class="trap-col-v3-2">田中</td>
            <td>28</td><td>営業部</td><td>090-XXXX-XXXX</td><td>tanaka@example.com</td><td>ITパスポート</td><td>2023/10/01</td><td>あああああああああああああああああああああああああああ</td>
          </tr>
          <tr>
            <td class="trap-col-v3-1">2</td>
            <td class="trap-col-v3-2">佐藤</td>
            <td>35</td><td>開発部</td><td>080-XXXX-XXXX</td><td>sato@example.com</td><td>基本情報技術者</td><td>2023/10/05</td><td>いいいいいいいいいいいいいいいいいいいいいいいいいいい</td>
          </tr>
        </table>
      </div>
    </div>

    <div class="tbl-col-v3-item">
      <p class="tbl-col-v3-label" style="color:#0d6efd;">⭕️ 成功(幅を固定 & leftをズラす)<br><small>文字の重なりがなくなり、スクロールしても綺麗についてくる!</small></p>
      
      <div class="tbl-x-scroll-v3-box">
        <div class="scroll-v3-hint" style="color: #0d6efd; border-color: #0d6efd;">👉 右へスクロールして違いを確認してください 👉</div>
        <table class="demo-x-v3-table">
          <tr>
            <th class="success-col-v3-1">ID</th>
            <th class="success-col-v3-2">名前</th>
            <th>年齢</th><th>部署</th><th>電話番号</th><th>メールアドレス</th><th>保有資格</th><th>最終ログイン</th><th>備考(ダミーテキストを長くして幅を広げています)</th>
          </tr>
          <tr>
            <td class="success-col-v3-1">1</td>
            <td class="success-col-v3-2">田中</td>
            <td>28</td><td>営業部</td><td>090-XXXX-XXXX</td><td>tanaka@example.com</td><td>ITパスポート</td><td>2023/10/01</td><td>あああああああああああああああああああああああああああ</td>
          </tr>
          <tr>
            <td class="success-col-v3-1">2</td>
            <td class="success-col-v3-2">佐藤</td>
            <td>35</td><td>開発部</td><td>080-XXXX-XXXX</td><td>sato@example.com</td><td>基本情報技術者</td><td>2023/10/05</td><td>いいいいいいいいいいいいいいいいいいいいいいいいいいい</td>
          </tr>
        </table>
      </div>
    </div>

  </div>

  <div class="tbl-col-v3-code-area">
    <span class="hl-comment">/* ❌ 固定したい列を全部 0 にしてしまう */</span><br>
    <span class="hl-blue">.col-1, .col-2</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">left:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 🚨 スクロールすると2列目が1列目の上に覆いかぶさる! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ 1列目を60pxに固定したら、2列目は「left: 60px」にする! */</span><br>
    <span class="hl-blue">.col-1</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">left:</span> <span class="hl-red">0;</span><br>
      <span class="hl-green">width:</span> <span class="hl-red">60px;</span> <span class="hl-comment">/* 💡 幅を絶対に縮まないよう固定する */</span><br>
      <span class="hl-green">box-sizing:</span> <span class="hl-red">border-box;</span><br>
    }<br>
    <span class="hl-blue">.col-2</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">left:</span> <span class="hl-red">60px;</span> <span class="hl-comment">/* 💡 1列目の幅の分だけ正確に右にズラす */</span><br>
    }
  </div>

</div>
CSSコード表示
.tbl-col-v3-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.tbl-col-v3-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.tbl-col-v3-demo-area {
  display: flex;
  flex-direction: column;
  gap: 30px;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.tbl-col-v3-item {
  width: 100%;
  text-align: left;
}

.tbl-col-v3-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* 横スクロールする枠 */
.tbl-x-scroll-v3-box {
  width: 100% !important;
  overflow-x: auto !important; /* 横スクロールを許可 */
  border: 2px solid #adb5bd !important;
  border-radius: 6px !important;
  background-color: #fff !important;
  position: relative !important;
}

/* スクロールを促すヒント表示 */
.scroll-v3-hint {
  padding: 10px !important;
  background-color: #fff !important;
  color: #dc3545 !important;
  font-weight: bold !important;
  font-size: 13px !important;
  text-align: center !important;
  border-bottom: 2px dashed #dc3545 !important;
  position: -webkit-sticky !important;
  position: sticky !important;
  left: 0 !important; /* スクロールしても常に左端に見えるようにする */
}

/* テーブル共通設定 */
.demo-x-v3-table {
  width: 1500px !important; /* 💡 どんな大画面でも絶対にスクロールさせるために巨大化! */
  min-width: 1500px !important;
  border-collapse: collapse !important;
  text-align: left !important;
  font-size: 14px !important;
  margin: 0 !important;
  table-layout: fixed !important; /* セルの幅を厳格に守らせる */
}

.demo-x-v3-table th,
.demo-x-v3-table td {
  padding: 15px 10px !important;
  border-right: 1px solid #dee2e6 !important;
  border-bottom: 1px solid #dee2e6 !important;
  background-color: #fff !important;
  box-sizing: border-box !important;
}

.demo-x-v3-table th {
  background-color: #f1f3f5 !important;
}

/* =❌ 罠:すべて left: 0(スクロールで重なる)= */
.trap-col-v3-1 {
  position: -webkit-sticky !important;
  position: sticky !important;
  left: 0 !important;
  width: 60px !important;
  background-color: #ffe6e6 !important;
  z-index: 2 !important;
}

.trap-col-v3-2 {
  position: -webkit-sticky !important;
  position: sticky !important;
  left: 0 !important; /* 🚨 ここがミス。1列目と同じ位置で固定しようとする */
  width: 80px !important;
  background-color: #e6f2ff !important;
  z-index: 1 !important;
}

/* =⭕️ 成功:幅を固定し、2列目はズラす= */
.success-col-v3-1 {
  position: -webkit-sticky !important;
  position: sticky !important;
  left: 0 !important;
  width: 60px !important; /* 💡 幅を絶対に縮まないように固定 */
  background-color: #ffe6e6 !important;
  z-index: 2 !important;
}

.success-col-v3-2 {
  position: -webkit-sticky !important;
  position: sticky !important;
  left: 60px !important; /* 💡 1列目の幅(60px)の分だけ正確にズラす */
  width: 80px !important;
  background-color: #e6f2ff !important;
  z-index: 2 !important; /* 1列目と同じ階層で並べる */
}

/* コードエリア */
.tbl-col-v3-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

.hl-blue {
  color: #61afef;
  font-weight: bold;
}

.hl-red {
  color: #e06c75;
  font-weight: bold;
}

.hl-green {
  color: #98c379;
  font-weight: bold;
}

.hl-comment {
  color: #6c757d;
  font-style: italic;
}

固定時にborder(枠線)が消える・透ける問題の対処法

テーブルのstickyにおいて、コーダーを苦しめてきたのがborderが消える仕様とスクロールした文字が透けてしまう問題です。

テーブルを固定する際は以下の2点です。

  1. 固定するセルにはbackground-colorで不透明な色を指定すること。
  2. 消えてしまうborderを使うのをやめ、box-shadowを使って枠線のように見える影を描画すること。
    (※影ならスクロールしても消えません。)

⭕️ stickyのセルは「背景色」を塗り、「box-shadow」で枠線を引け!

❌ 罠(背景透明 & border使用)
文字が重なり、スクロールすると枠線が消える

見出し 項目A
裏の文字裏の文字
裏の文字裏の文字
裏の文字裏の文字

⭕️ 成功(背景色あり & box-shadow使用)
文字は透けず、枠線もスクロールに耐える!

見出し 項目A
裏の文字裏の文字
裏の文字裏の文字
裏の文字裏の文字
/* ❌ 背景色を忘れ、通常の枠線を使ってしまう */
th.trap {
  position: sticky;
  top: 0;
  /* 🚨 背景がないので透ける。border-collapse下では border は消える */
  border-bottom: 1px solid #ccc;
}

/* ⭕️ 背景色を塗り、枠線は box-shadow で再現する! */
th.success {
  position: sticky;
  top: 0;
  background-color: #0d6efd; /* 💡 背景を不透明にして透けを防止 */
  box-shadow: 0 1px 0 #333; /* 💡 下向きに「1pxの影」を落として枠線を偽装する(絶対に消えない) */
}
HTMLコード表示
<div class="tbl-bug-wrapper">
  
  <p class="tbl-bug-caption">⭕️ stickyのセルは「背景色」を塗り、「box-shadow」で枠線を引け!</p>

  <div class="tbl-bug-demo-area">
    
    <div class="tbl-bug-item">
      <p class="tbl-bug-label" style="color:#dc3545;">❌ 罠(背景透明 & border使用)<br><small>文字が重なり、スクロールすると枠線が消える</small></p>
      
      <div class="tbl-bug-scroll-box">
        <table class="demo-bug-table is-collapse">
          <tr>
            <th class="is-trap-bug">見出し</th>
            <th class="is-trap-bug">項目A</th>
          </tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
        </table>
      </div>
    </div>

    <div class="tbl-bug-item">
      <p class="tbl-bug-label" style="color:#0d6efd;">⭕️ 成功(背景色あり & box-shadow使用)<br><small>文字は透けず、枠線もスクロールに耐える!</small></p>
      
      <div class="tbl-bug-scroll-box">
        <table class="demo-bug-table is-collapse">
          <tr>
            <th class="is-success-bug">見出し</th>
            <th class="is-success-bug">項目A</th>
          </tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
          <tr><td>裏の文字</td><td>裏の文字</td></tr>
        </table>
      </div>
    </div>

  </div>

  <div class="tbl-bug-code-area">
    <span class="hl-comment">/* ❌ 背景色を忘れ、通常の枠線を使ってしまう */</span><br>
    <span class="hl-blue">th.trap</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
      <span class="hl-comment">/* 🚨 背景がないので透ける。border-collapse下では border は消える */</span><br>
      <span class="hl-green">border-bottom:</span> <span class="hl-red">1px solid #ccc;</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ 背景色を塗り、枠線は box-shadow で再現する! */</span><br>
    <span class="hl-blue">th.success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
      <span class="hl-green">background-color:</span> <span class="hl-red">#0d6efd;</span> <span class="hl-comment">/* 💡 背景を不透明にして透けを防止 */</span><br>
      <span class="hl-green">box-shadow:</span> <span class="hl-red">0 1px 0 #333;</span> <span class="hl-comment">/* 💡 下向きに「1pxの影」を落として枠線を偽装する(絶対に消えない) */</span><br>
    }
  </div>

</div>
CSSコード表示
.tbl-bug-wrapper { background-color: #f8f9fa; padding: 30px; border-radius: 8px; border: 1px solid #dee2e6; }
.tbl-bug-caption { font-size: 14px; font-weight: bold; margin-bottom: 20px; color: #198754; text-align: center; }
.tbl-bug-demo-area { display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; background-color: #e9ecef; padding: 30px 20px; border-radius: 8px; border: 1px dashed #adb5bd; margin-bottom: 20px; }
.tbl-bug-item { width: 250px; text-align: center; }
.tbl-bug-label { font-size: 13px; font-weight: bold; color: #333; margin-bottom: 10px; line-height: 1.5; }

/* スクロールする枠 */
.tbl-bug-scroll-box {
  height: 120px !important;
  overflow-y: auto !important;
  border: 2px solid #adb5bd !important;
  border-radius: 6px !important;
  background-color: #fff !important;
  position: relative !important;
}

/* テーブル共通(border-collapseのバグを再現) */
.demo-bug-table {
  width: 100% !important;
  border-collapse: collapse !important; /* 🚨 これがあるとstickyのborderが消える */
  text-align: center !important;
  font-size: 14px !important;
}
.demo-bug-table td {
  padding: 15px 10px !important;
  border-bottom: 1px solid #dee2e6 !important;
  color: #6c757d !important;
}

/* =❌ 罠:背景透明&通常border= */
.is-trap-bug {
  position: sticky !important;
  top: 0 !important;
  padding: 10px !important;
  color: #0d6efd !important;
  font-weight: bold !important;
  /* 背景色なし(透明) */
  border-bottom: 2px solid #dc3545 !important; /* 🚨 スクロールすると消える */
}

/* =⭕️ 成功:背景色あり&box-shadow= */
.is-success-bug {
  position: sticky !important;
  top: 0 !important;
  padding: 10px !important;
  color: #fff !important;
  font-weight: bold !important;
  background-color: #0d6efd !important; /* 💡 背景を塗りつぶす */
  /* border-bottom は使わない */
  /* 💡 下向きにのみ1pxの影を落としてボーダーに見せる */
  box-shadow: 0 2px 0 #0a58ca !important; 
  z-index: 1 !important;
}

/* コードエリア */
.tbl-bug-code-area { background-color: #282c34; color: #abb2bf; padding: 20px; border-radius: 6px; font-family: monospace; font-size: 13px; line-height: 1.6; border-left: 4px solid #0d6efd; overflow-x: auto; text-align: left; }

影を実現するbox-shadowの使い方を詳しく知りたい人は「【CSS】box-shadowの使い方:おしゃれな影のコピペ集と浮遊感の作り方」を一読ください。

stickyが効かない!原因とチェックリスト

「CSSでposition: sticky;を指定したのに、stickyが効かない!」

「検索しても解決しない……」

position: sticky;は便利なプロパティですが、「CSSの中でもトップクラスに条件が厳しく、1つでもルールを破ると機能停止する」という厄介な特徴を持っています。

ここでは、実務でハマりやすい4つの原因と解決策(チェックリスト)を解説します。

stickyが効かない!原因とチェックリスト
  • 親・先祖要素にoverflow: hiddenがある
  • 親要素に高さがない・子要素と同じ高さになっている
  • topbottomの指定忘れ/z-indexで裏に隠れている
  • Safari(iOS)特有のバグとベンダープレフィックス

親・先祖要素にoverflow: hiddenがある

stickyが効かない原因の第1位が「親要素、あるいはさらに上の先祖要素のどこかにoverflow: hiddenが指定されている」という問題です。

CSSの仕様上、先祖要素にoverflowがかかっていると、新しい「スクロール領域の限界」として認識されてしまうため、画面全体に対するstickyが無効化されます。

効かない時は、「ブラウザの検証ツール(デベロッパーツール)を開き、sticky要素から<body>タグに至るまでのすべての親・先祖要素を辿り、overflowが隠れていないか調査すること」です。

⭕️ stickyの親や先祖に「overflow: hidden」があると絶対に動かない!

❌ 罠(親にoverflow: hidden)
stickyが完全に殺され、一緒に流れる

❌ 追従しない
親要素のコンテンツ
スクロールしても…
追従しません。
下に流れます。

⭕️ 成功(overflow: visible)
制限から解放され、綺麗に追従する!

⭕️ 追従する!
親要素のコンテンツ
スクロールすると…
ピタッと!
ついてきます。
/* ❌ 親要素のどこかに overflow をかけてしまう */
.parent-fail {
  overflow: hidden; /* 🚨 どんなに深くても、先祖にコレがあると終了 */
}

/* ⭕️ 親から body に至るまで、overflow は visible(初期値)にする! */
.parent-success {
  overflow: visible; /* 💡 これならstickyは正常に動く */
}
HTMLコード表示
<div class="chk-ovf-wrapper">
  
  <p class="chk-ovf-caption">⭕️ stickyの親や先祖に「overflow: hidden」があると絶対に動かない!</p>

  <div class="chk-ovf-demo-area">
    
    <div class="chk-ovf-item">
      <p class="chk-ovf-label" style="color:#dc3545;">❌ 罠(親にoverflow: hidden)<br><small>stickyが完全に殺され、一緒に流れる</small></p>
      
      <div class="scroll-ovf-box">
        <div class="parent-trap-ovf">
          <div class="st-ovf-box is-trap-ovf">
            ❌ 追従しない
          </div>
          <div class="dummy-text-ovf">親要素のコンテンツ</div>
          <div class="dummy-text-ovf">スクロールしても...</div>
          <div class="dummy-text-ovf">追従しません。</div>
          <div class="dummy-text-ovf">下に流れます。</div>
        </div>
      </div>
    </div>

    <div class="chk-ovf-item">
      <p class="chk-ovf-label" style="color:#0d6efd;">⭕️ 成功(overflow: visible)<br><small>制限から解放され、綺麗に追従する!</small></p>
      
      <div class="scroll-ovf-box">
        <div class="parent-success-ovf">
          <div class="st-ovf-box is-success-ovf">
            ⭕️ 追従する!
          </div>
          <div class="dummy-text-ovf">親要素のコンテンツ</div>
          <div class="dummy-text-ovf">スクロールすると...</div>
          <div class="dummy-text-ovf">ピタッと!</div>
          <div class="dummy-text-ovf">ついてきます。</div>
        </div>
      </div>
    </div>

  </div>

  <div class="chk-ovf-code-area">
    <span class="hl-comment">/* ❌ 親要素のどこかに overflow をかけてしまう */</span><br>
    <span class="hl-blue">.parent-fail</span> {<br>
      <span class="hl-green">overflow:</span> <span class="hl-red">hidden;</span> <span class="hl-comment">/* 🚨 どんなに深くても、先祖にコレがあると終了 */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ 親から body に至るまで、overflow は visible(初期値)にする! */</span><br>
    <span class="hl-blue">.parent-success</span> {<br>
      <span class="hl-green">overflow:</span> <span class="hl-red">visible;</span> <span class="hl-comment">/* 💡 これならstickyは正常に動く */</span><br>
    }
  </div>

</div>
CSSコード表示
.chk-ovf-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.chk-ovf-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.chk-ovf-demo-area {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.chk-ovf-item {
  width: 250px;
  text-align: center;
}

.chk-ovf-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* デモ用のスクロール大枠 */
.scroll-ovf-box {
  height: 200px;
  overflow-y: auto;
  background-color: #fff;
  border: 2px solid #adb5bd;
  border-radius: 6px;
  position: relative;
  text-align: left;
}

/* ダミーのテキスト */
.dummy-text-ovf {
  padding: 20px;
  color: #6c757d;
  border-bottom: 1px dashed #ccc;
}

/* =❌ 罠:親にoverflow: hidden= */
.parent-trap-ovf {
  background-color: #f8d7da;
  padding: 10px;
  overflow: hidden; /* 🚨 これがstickyを殺す元凶 */
}

.is-trap-ovf {
  position: -webkit-sticky;
  position: sticky;
  top: 10px;
  background-color: #dc3545;
  color: #fff;
  padding: 10px;
  font-weight: bold;
  text-align: center;
  border-radius: 4px;
}

/* =⭕️ 成功:親が通常状態= */
.parent-success-ovf {
  background-color: #d1e7dd;
  padding: 10px;
  overflow: visible; /* 💡 何も制限しない */
}

.is-success-ovf {
  position: -webkit-sticky;
  position: sticky;
  top: 10px;
  background-color: #0d6efd;
  color: #fff;
  padding: 10px;
  font-weight: bold;
  text-align: center;
  border-radius: 4px;
}

/* =コード解説エリア= */
.chk-ovf-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

.hl-blue {
  color: #61afef;
  font-weight: bold;
}

.hl-red {
  color: #e06c75;
  font-weight: bold;
}

.hl-green {
  color: #98c379;
  font-weight: bold;
}

.hl-comment {
  color: #6c757d;
  font-style: italic;
}

親要素に高さがない・子要素と同じ高さになっている

次によくある原因が、「親要素の高さ」に関する問題です。

stickyは、「親要素の範囲内でのみ、滑り降りるように固定される」という仕様です。

つまり、親要素の中に「滑り降りるための余白(高さの余裕)」がないと、全く固定されません。

親要素の高さ不足を解決するには、「Flexboxの子要素にstickyを効かせる場合は、その要素自身にalign-self: flex-start;を指定して、高さを本来のサイズに縮めて隙間を作ること」です。

⭕️ stickyが動くには「親要素の中を滑り降りるための隙間」が必要!

❌ 罠(stretchで高さが同じ)
隙間がないため、その場で身動きが取れない

メインが長い




終わり
❌ 隙間なし

⭕️ 成功(flex-startで隙間を作る)
本来の高さに縮むことで、滑り降りる余裕ができる!

メインが長い




終わり
⭕️ 追従する!
/* ❌ Flexboxのデフォルト(stretch)をそのままにする */
.sidebar-fail {
  position: sticky;
  top: 10px; /* 🚨 親と同じ高さに引き伸ばされているため、動けない! */
}

/* ⭕️ align-self で自分の高さを縮め、隙間(余白)を作る! */
.sidebar-success {
  align-self: flex-start; /* 💡 高さが元に戻り、下へ滑り降りる余裕ができる */
  position: sticky;
  top: 10px;
}
HTMLコード表示
<div class="chk-hgt-wrapper">
  
  <p class="chk-hgt-caption">⭕️ stickyが動くには「親要素の中を滑り降りるための隙間」が必要!</p>

  <div class="chk-hgt-demo-area">
    
    <div class="chk-hgt-item">
      <p class="chk-hgt-label" style="color:#dc3545;">❌ 罠(stretchで高さが同じ)<br><small>隙間がないため、その場で身動きが取れない</small></p>
      
      <div class="scroll-hgt-box">
        <div class="flex-hgt-trap">
          <div class="main-hgt-content">
            メインが長い<br>↓<br>↓<br>↓<br>↓<br>終わり
          </div>
          <div class="st-hgt-sidebar is-trap-hgt">
            ❌ 隙間なし
          </div>
        </div>
      </div>
    </div>

    <div class="chk-hgt-item">
      <p class="chk-hgt-label" style="color:#0d6efd;">⭕️ 成功(flex-startで隙間を作る)<br><small>本来の高さに縮むことで、滑り降りる余裕ができる!</small></p>
      
      <div class="scroll-hgt-box">
        <div class="flex-hgt-success">
          <div class="main-hgt-content">
            メインが長い<br>↓<br>↓<br>↓<br>↓<br>終わり
          </div>
          <div class="st-hgt-sidebar is-success-hgt">
            ⭕️ 追従する!
          </div>
        </div>
      </div>
    </div>

  </div>

  <div class="chk-hgt-code-area">
    <span class="hl-comment">/* ❌ Flexboxのデフォルト(stretch)をそのままにする */</span><br>
    <span class="hl-blue">.sidebar-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">10px;</span> <span class="hl-comment">/* 🚨 親と同じ高さに引き伸ばされているため、動けない! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ align-self で自分の高さを縮め、隙間(余白)を作る! */</span><br>
    <span class="hl-blue">.sidebar-success</span> {<br>
      <span class="hl-green">align-self:</span> <span class="hl-red">flex-start;</span> <span class="hl-comment">/* 💡 高さが元に戻り、下へ滑り降りる余裕ができる */</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">10px;</span><br>
    }
  </div>

</div>
CSSコード表示
.chk-hgt-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.chk-hgt-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.chk-hgt-demo-area {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.chk-hgt-item {
  width: 250px;
  text-align: center;
}

.chk-hgt-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* デモ用のスクロール大枠 */
.scroll-hgt-box {
  height: 200px;
  overflow-y: auto;
  background-color: #fff;
  border: 2px solid #adb5bd;
  border-radius: 6px;
  text-align: left;
}

/* メインコンテンツ(高さを稼ぐ役) */
.main-hgt-content {
  flex-grow: 1;
  background-color: #f1f3f5;
  padding: 10px;
  border: 1px dashed #ccc;
  line-height: 2;
  color: #555;
  font-size: 12px;
}

/* 共通のサイドバースタイル */
.st-hgt-sidebar {
  width: 90px;
  padding: 10px;
  color: #fff;
  font-weight: bold;
  text-align: center;
  border-radius: 4px;
}

/* =❌ 罠:stretchで高さがパツパツ= */
.flex-hgt-trap {
  display: flex;
  gap: 10px;
  padding: 10px;
  /* align-items: stretch がデフォルトで効いている */
}

.is-trap-hgt {
  position: -webkit-sticky;
  position: sticky;
  top: 10px;
  background-color: #dc3545;
  /* 🚨 高さがメインコンテンツと同じになってしまうため動けない */
}

/* =⭕️ 成功:flex-startで高さを縮める= */
.flex-hgt-success {
  display: flex;
  gap: 10px;
  padding: 10px;
}

.is-success-hgt {
  align-self: flex-start; /* 💡 自身の本来の高さに縮む */
  position: -webkit-sticky;
  position: sticky;
  top: 10px;
  background-color: #0d6efd;
}

/* =コード解説エリア= */
.chk-hgt-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

top・bottomの指定忘れ/z-indexで裏に隠れている

うっかりミスとして多いのが、「topなどの座標(閾値)の指定忘れ」と「z-indexの指定忘れによる裏被り」です。

position: sticky;だけを記述しても、「どこに張り付くか」が分からないため無効になります。

また、座標を書いて固定に成功しても、重なる問題が発生し、後から出てくる画像やテキストにヘッダーが下敷きにされてしまうことが多々あります。

追従要素を作った時は、「position: sticky;top: 0;z-index: 100;の3つは、セットとして記述すること」です。

⭕️ stickyには「座標(top)」と「z-index」が絶対に必要不可欠!

❌ 罠(z-indexなし)
下の画像(relative)が上に被さる!

❌ 下敷きになるヘッダー
スクロールしてください
画像がヘッダーの上に乗る!
終わり

⭕️ 成功(z-indexあり)
どんな要素が来ても最前面をキープ!

⭕️ 最前面のヘッダー
スクロールしてください
画像はヘッダーの下に潜る!
終わり
/* ❌ z-indexを書き忘れる */
.header-fail {
  position: sticky;
  top: 0; /* 🚨 これだけだと、relativeが指定された画像の裏に隠れる! */
}

/* ⭕️ sticky・top・z-index は三種の神器!必ずセットで書く! */
.header-success {
  position: sticky;
  top: 0;
  z-index: 100; /* 💡 これで絶対に下敷きにならない最前面の盾が完成する */
}
HTMLコード表示
<div class="chk-z-wrapper">
  
  <p class="chk-z-caption">⭕️ stickyには「座標(top)」と「z-index」が絶対に必要不可欠!</p>

  <div class="chk-z-demo-area">
    
    <div class="chk-z-item">
      <p class="chk-z-label" style="color:#dc3545;">❌ 罠(z-indexなし)<br><small>下の画像(relative)が上に被さる!</small></p>
      
      <div class="scroll-z-box">
        <div class="st-z-header is-trap-z">
          ❌ 下敷きになるヘッダー
        </div>
        <div class="dummy-z-text">スクロールしてください</div>
        <div class="dummy-z-text">↓</div>
        
        <div class="dummy-z-img">
          画像がヘッダーの上に乗る!
        </div>
        
        <div class="dummy-z-text">↓</div>
        <div class="dummy-z-text">終わり</div>
      </div>
    </div>

    <div class="chk-z-item">
      <p class="chk-z-label" style="color:#0d6efd;">⭕️ 成功(z-indexあり)<br><small>どんな要素が来ても最前面をキープ!</small></p>
      
      <div class="scroll-z-box">
        <div class="st-z-header is-success-z">
          ⭕️ 最前面のヘッダー
        </div>
        <div class="dummy-z-text">スクロールしてください</div>
        <div class="dummy-z-text">↓</div>
        
        <div class="dummy-z-img">
          画像はヘッダーの下に潜る!
        </div>
        
        <div class="dummy-z-text">↓</div>
        <div class="dummy-z-text">終わり</div>
      </div>
    </div>

  </div>

  <div class="chk-z-code-area">
    <span class="hl-comment">/* ❌ z-indexを書き忘れる */</span><br>
    <span class="hl-blue">.header-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 🚨 これだけだと、relativeが指定された画像の裏に隠れる! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ sticky・top・z-index は三種の神器!必ずセットで書く! */</span><br>
    <span class="hl-blue">.header-success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
      <span class="hl-green">z-index:</span> <span class="hl-red">100;</span> <span class="hl-comment">/* 💡 これで絶対に下敷きにならない最前面の盾が完成する */</span><br>
    }
  </div>

</div>
CSSコード表示
.chk-z-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.chk-z-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.chk-z-demo-area {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.chk-z-item {
  width: 250px;
  text-align: center;
}

.chk-z-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* デモ用のスクロール大枠 */
.scroll-z-box {
  height: 200px;
  overflow-y: auto;
  background-color: #fff;
  border: 2px solid #adb5bd;
  border-radius: 6px;
  position: relative;
  text-align: center;
}

/* 共通のヘッダースタイル */
.st-z-header {
  padding: 15px 10px;
  color: #fff;
  font-weight: bold;
}

/* ダミーのテキストと画像(relative) */
.dummy-z-text {
  padding: 20px;
  color: #6c757d;
}

.dummy-z-img {
  position: relative; /* 🚨 重なりの原因となる相対配置 */
  z-index: 10;
  background-color: #ffc107;
  color: #333;
  font-weight: bold;
  padding: 30px 10px;
  border: 2px dashed #d39e00;
  margin: 10px;
}

/* =❌ 罠:z-indexなし= */
.is-trap-z {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  background-color: #dc3545;
  /* 🚨 z-indexがないため、下の画像の z-index:10 に負ける */
}

/* =⭕️ 成功:z-indexあり= */
.is-success-z {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  z-index: 100; /* 💡 十分に高い数値を指定する */
  background-color: #0d6efd;
  box-shadow: 0 4px 6px rgba(0,0,0,0.2);
}

/* =コード解説エリア= */
.chk-z-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

Safari(iOS)特有のバグとベンダープレフィックス

チェックリストの最後は、ブラウザ間の互換性問題です。

「WindowsのChromeでは動いているのに、iPhoneで見たら全く固定されていない!」という悲劇は、ベンダープレフィックスの指定忘れが原因です。

古いiOSのSafariや一部のマイナーなブラウザ環境では、標準のposition: sticky;だけでは認識されません。

iPhone(Safari)への対応は、「先に-webkit-stickyを書き、直下の行に標準のstickyを書くこと」です。

これで、Safariは上の行を読み込み、モダンブラウザは下の標準仕様で上書きしてくれます。

⭕️ iPhone(Safari)で泣かないために、プレフィックスは必須!

❌ 罠(プレフィックスなし)
古いiPhone等で見た時に固定されない

❌ position: sticky; のみ

⭕️ 成功(正しい順序で記述)
すべてのブラウザ・スマホで安全に稼働する

⭕️ -webkit- を先に書く
/* ❌ Chromeでしか確認せず、プレフィックスを忘れる */
.item-fail {
  position: sticky; /* 🚨 古いiOS Safariでは無視されてしまう! */
}

/* ⭕️ 先に -webkit- を書き、次に標準仕様を書く! */
.item-success {
  position: -webkit-sticky; /* 💡 Safari用の指定(先) */
  position: sticky; /* 💡 標準仕様(後。モダンブラウザはこっちで上書き) */
  top: 0;
}
HTMLコード表示
<div class="chk-safari-wrapper">
  
  <p class="chk-safari-caption">⭕️ iPhone(Safari)で泣かないために、プレフィックスは必須!</p>

  <div class="chk-safari-demo-area">
    
    <div class="chk-safari-item">
      <p class="chk-safari-label" style="color:#dc3545;">❌ 罠(プレフィックスなし)<br><small>古いiPhone等で見た時に固定されない</small></p>
      
      <div class="safari-box is-trap-safari">
        ❌ position: sticky; のみ
      </div>
    </div>

    <div class="chk-safari-item">
      <p class="chk-safari-label" style="color:#0d6efd;">⭕️ 成功(正しい順序で記述)<br><small>すべてのブラウザ・スマホで安全に稼働する</small></p>
      
      <div class="safari-box is-success-safari">
        ⭕️ -webkit- を先に書く
      </div>
    </div>

  </div>

  <div class="chk-safari-code-area">
    <span class="hl-comment">/* ❌ Chromeでしか確認せず、プレフィックスを忘れる */</span><br>
    <span class="hl-blue">.item-fail</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span> <span class="hl-comment">/* 🚨 古いiOS Safariでは無視されてしまう! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ 先に -webkit- を書き、次に標準仕様を書く! */</span><br>
    <span class="hl-blue">.item-success</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">-webkit-sticky;</span> <span class="hl-comment">/* 💡 Safari用の指定(先) */</span><br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span> <span class="hl-comment">/* 💡 標準仕様(後。モダンブラウザはこっちで上書き) */</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
    }
  </div>

</div>
CSSコード表示
.chk-safari-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}

.chk-safari-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}

.chk-safari-demo-area {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

.chk-safari-item {
  width: 250px;
  text-align: center;
}

.chk-safari-label {
  font-size: 13px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  line-height: 1.5;
}

/* 見た目のダミーボックス(動きではなく記述の啓蒙デモ) */
.safari-box {
  padding: 20px 10px;
  border-radius: 6px;
  font-weight: bold;
  color: #fff;
  font-size: 14px;
}

/* =❌ 罠:プレフィックスなし= */
.is-trap-safari {
  background-color: #dc3545;
  border: 2px dashed #842029;
}

/* =⭕️ 成功:正しい順序= */
.is-success-safari {
  background-color: #0d6efd;
  border: 2px solid #084298;
}

/* =コード解説エリア= */
.chk-safari-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

複数要素のスタック・JS連携・Flexbox・Grid

基礎と各パーツごとの仕様をマスターしたら、実務で求められる高度なUI実装に入ります。

「ヘッダーの下にもう一つ別のメニューを固定したい」「固定された瞬間に影を出してフワッと浮かせたい」「Gridレイアウトの中で使いたい」といった複雑な要望が当たり前のように飛び交います。

ここでは、要素の高度な制御、JavaScriptと連携したスタイルの動的変更、Grid/Flexboxと組み合わせる際のテクニックを解説します。

複数要素のスタック・JS連携・Flexbox・Grid
  • 複数のsticky要素を段差で重ねる
  • スクロールで固定された時に影をつける
  • Grid/Flexbox環境でのstickyの使い方

複数のsticky要素を段差で重ねる

「サイト全体のメインヘッダー」が上部(top: 0)に固定されている状態で、さらにスクロールすると「記事の目次メニュー」や「カテゴリタブ」がそのすぐ下に追従してくる…といった複数要素の固定は人気のあるUIです。

また、スクロールするたびにアルファベット順の見出し(A、B、C…)が次々と入れ替わるように固定される実装もこの応用になります。

複数のsticky要素を段差で重ねるには、「2つ目以降の要素のtop値は、上に固定されている要素の『高さ』の分だけ数値をズラして指定すること」です。

⭕️ 2つ目のstickyは、1つ目の「高さの分」だけtopをズラせ!

❌ 罠(すべて top: 0)
サブがメインの上に完全に覆いかぶさる

メインヘッダー
スクロール ↓
サブヘッダー (top: 0)
(コンテンツ)

⭕️ 成功(top: 50px にズラす)
メインの下に綺麗にピタッと並んで固定!

メインヘッダー
スクロール ↓
サブヘッダー (top: 50px)
(コンテンツ)
/* ❌ 全ての上部余白を 0 にしてしまう */
.header-main, .header-sub {
  position: sticky;
  top: 0; /* 🚨 サブヘッダーがメインヘッダーに激突し、上に乗ってしまう! */
}

/* ⭕️ メインの高さ(50px)の分だけ、サブのtopをズラす! */
.header-main {
  position: sticky;
  top: 0;
  height: 50px; /* 💡 高さをきっちり決める */
}
.header-sub {
  position: sticky;
  top: 50px; /* 💡 50pxの位置から固定を開始させる */
}
HTMLコード表示
<div class="stk-multi-wrapper">
  
  <p class="stk-multi-caption">⭕️ 2つ目のstickyは、1つ目の「高さの分」だけtopをズラせ!</p>

  <div class="stk-multi-demo-area">
    
    <div class="stk-multi-item">
      <p class="stk-multi-label trap-color">❌ 罠(すべて top: 0)<br><span class="label-small">サブがメインの上に完全に覆いかぶさる</span></p>
      
      <div class="scroll-multi-box">
        <div class="multi-main-header">
          メインヘッダー
        </div>
        
        <div class="dummy-multi-space-small">スクロール ↓</div>
        
        <div class="multi-sub-header is-trap-multi">
          サブヘッダー (top: 0)
        </div>
        
        <div class="dummy-multi-space-large">(コンテンツ)</div>
      </div>
    </div>

    <div class="stk-multi-item">
      <p class="stk-multi-label success-color">⭕️ 成功(top: 50px にズラす)<br><span class="label-small">メインの下に綺麗にピタッと並んで固定!</span></p>
      
      <div class="scroll-multi-box">
        <div class="multi-main-header">
          メインヘッダー
        </div>
        
        <div class="dummy-multi-space-small">スクロール ↓</div>
        
        <div class="multi-sub-header is-success-multi">
          サブヘッダー (top: 50px)
        </div>
        
        <div class="dummy-multi-space-large">(コンテンツ)</div>
      </div>
    </div>

  </div>

  <div class="stk-multi-code-area">
    <span class="hl-comment">/* ❌ 全ての上部余白を 0 にしてしまう */</span><br>
    <span class="hl-blue">.header-main, .header-sub</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span> <span class="hl-comment">/* 🚨 サブヘッダーがメインヘッダーに激突し、上に乗ってしまう! */</span><br>
    }<br><br>

    <span class="hl-comment">/* ⭕️ メインの高さ(50px)の分だけ、サブのtopをズラす! */</span><br>
    <span class="hl-blue">.header-main</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">0;</span><br>
      <span class="hl-green">height:</span> <span class="hl-red">50px;</span> <span class="hl-comment">/* 💡 高さをきっちり決める */</span><br>
    }<br>
    <span class="hl-blue">.header-sub</span> {<br>
      <span class="hl-green">position:</span> <span class="hl-red">sticky;</span><br>
      <span class="hl-green">top:</span> <span class="hl-red">50px;</span> <span class="hl-comment">/* 💡 50pxの位置から固定を開始させる */</span><br>
    }
  </div>

</div>
CSSコード表示
.stk-multi-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}
.stk-multi-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}
.stk-multi-demo-area {
  display: flex;
  flex-wrap: wrap;
  gap: 30px;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}
.stk-multi-item {
  width: 250px;
  text-align: center;
}
.stk-multi-label {
  font-size: 13px;
  font-weight: bold;
  margin-bottom: 10px;
  line-height: 1.5;
}
.trap-color {
  color: #dc3545;
}
.success-color {
  color: #0d6efd;
}
.label-small {
  font-size: 11px;
  font-weight: normal;
  color: #555;
}

/* スクロールエリア */
.scroll-multi-box {
  height: 250px;
  overflow-y: auto;
  background-color: #fff;
  border: 2px solid #adb5bd;
  border-radius: 6px;
  position: relative;
}

/* =💡 1つ目:メインヘッダー= */
.multi-main-header {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  height: 50px; /* 💡 高さを固定 */
  background-color: #343a40;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  z-index: 10;
}

/* 共通のサブヘッダー */
.multi-sub-header {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 14px;
  z-index: 9; /* メインより1つ下げる */
}

/* =❌ 罠:top:0= */
.is-trap-multi {
  position: -webkit-sticky;
  position: sticky;
  top: 0; /* 🚨 メインヘッダーと同じ位置になるため覆いかぶさる */
  background-color: #dc3545;
  color: #fff;
}

/* =⭕️ 成功:top:50px= */
.is-success-multi {
  position: -webkit-sticky;
  position: sticky;
  top: 50px; /* 💡 メインの高さ(50)の分だけ正確にズラす */
  background-color: #0d6efd;
  color: #fff;
  box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}

/* ダミーの空白要素 */
.dummy-multi-space-small {
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6c757d;
  background-color: #f8f9fa;
}
.dummy-multi-space-large {
  height: 400px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 20px;
  color: #adb5bd;
}

/* コードエリア */
.stk-multi-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}

スクロールで固定された時に影をつける

ヘッダーがページの一番上にある時は背景と馴染ませておき、下にスクロールして 「画面に張り付いた瞬間にだけ、影をつけて浮き上がらせる」 という演出は、大手企業のサイトなどでよく使われます。

また、固定時に背景色を変えることで視認性を高めることもあります。

しかし、CSSのsticky単体には「自分が今固定されているかどうか」を判定する擬似クラス(例えば:stuckのようなもの)が存在しません。

そのため、状態を検知するにはJavaScriptとの連携が必須になります。

固定された瞬間を検知してスタイルを変えるには、「重いscrollイベントは使わず、代わりにIntersectionObserverAPIを使うことで、ヘッダーのすぐ上に『1pxの透明な監視用ダミー要素』を置き、それが画面から見えなくなった瞬間にクラスを付与すること」です。

⭕️ 重いスクロールイベントはNG!透明な監視役(Sentinel)を配置しろ!

スクロールすると影が出ます

↓ ゆっくり下にスクロール ↓

ヘッダーが上端に張り付くと…

JSが検知してクラスを付与し、

フワッと影(shadow)が出現します!

(さらに下へ)

(さらに下へ)

(さらに下へ)

<!– ⭕️ ヘッダーの直前に監視用の要素を置く –>
<div class=“sentinel”></div>
<header id=“myHeader”>ヘッダー</header>

// JS:IntersectionObserver で監視する(超軽量)
const observer = new IntersectionObserver(entries => {
  if (!entries[0].isIntersecting) {
    // 💡 監視役が画面外に出た = ヘッダーが固定された!
    header.classList.add(‘is-stuck’);
  } else {
    // 💡 監視役が画面に戻った = 固定解除!
    header.classList.remove(‘is-stuck’);
  }
});
observer.observe(document.querySelector(‘.sentinel’));
HTMLコード表示
<div class="stk-js-wrapper">
  
  <p class="stk-js-caption">⭕️ 重いスクロールイベントはNG!透明な監視役(Sentinel)を配置しろ!</p>

  <div class="stk-js-demo-area">
    
    <div class="scroll-js-box">
      
      <div id="js-sentinel" class="sentinel-element"></div>
      
      <div id="js-sticky-header" class="dynamic-sticky-header">
        スクロールすると影が出ます
      </div>
      
      <div class="dummy-js-content">
        <p>↓ ゆっくり下にスクロール ↓</p>
        <p>ヘッダーが上端に張り付くと...</p>
        <p>JSが検知してクラスを付与し、</p>
        <p>フワッと影(shadow)が出現します!</p>
        <p>(さらに下へ)</p>
        <p>(さらに下へ)</p>
        <p>(さらに下へ)</p>
      </div>
    </div>

  </div>

  <div class="stk-js-code-area">
    <span class="hl-comment"><!-- ⭕️ ヘッダーの直前に監視用の要素を置く --></span><br>
    <span class="hl-blue"><div <span class="hl-green">class=</span><span class="hl-red">"sentinel"</span>></div></span><br>
    <span class="hl-blue"><header <span class="hl-green">id=</span><span class="hl-red">"myHeader"</span>></span>ヘッダー<span class="hl-blue"></header></span><br><br>

    <span class="hl-comment">// JS:IntersectionObserver で監視する(超軽量)</span><br>
    <span class="hl-blue">const</span> <span class="hl-green">observer</span> = <span class="hl-blue">new</span> <span class="hl-green">IntersectionObserver</span>(<span class="hl-red">entries</span> => {<br>
      <span class="hl-blue">if</span> (!entries[0].isIntersecting) {<br>
        <span class="hl-comment">// 💡 監視役が画面外に出た = ヘッダーが固定された!</span><br>
        header.classList.add(<span class="hl-red">'is-stuck'</span>);<br>
      } <span class="hl-blue">else</span> {<br>
        <span class="hl-comment">// 💡 監視役が画面に戻った = 固定解除!</span><br>
        header.classList.remove(<span class="hl-red">'is-stuck'</span>);<br>
      }<br>
    });<br>
    observer.observe(<span class="hl-green">document</span>.querySelector(<span class="hl-red">'.sentinel'</span>));
  </div>

</div>
CSSコード表示
.stk-js-wrapper {
  background-color: #f8f9fa;
  padding: 30px;
  border-radius: 8px;
  border: 1px solid #dee2e6;
}
.stk-js-caption {
  font-size: 14px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #198754;
  text-align: center;
}
.stk-js-demo-area {
  display: flex;
  justify-content: center;
  background-color: #e9ecef;
  padding: 30px 20px;
  border-radius: 8px;
  border: 1px dashed #adb5bd;
  margin-bottom: 20px;
}

/* スクロールエリア */
.scroll-js-box {
  width: 300px;
  height: 250px;
  overflow-y: auto;
  background-color: #fff;
  border: 2px solid #adb5bd;
  border-radius: 6px;
  position: relative;
}

/* 💡 監視用ダミー要素(見えないように配置) */
.sentinel-element {
  width: 100%;
  height: 1px;
  background-color: transparent;
}

/* 💡 stickyヘッダーの初期状態 */
.dynamic-sticky-header {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  padding: 15px;
  background-color: #e9ecef; /* 初期は薄いグレー */
  color: #495057;
  font-weight: bold;
  text-align: center;
  transition: all 0.3s ease; /* フワッと変化させるためのtransition */
  z-index: 100;
  box-shadow: 0 0 0 rgba(0,0,0,0); /* 初期は影なし */
}

/* 💡 JSによって付与されるクラス(固定された時のスタイル) */
.is-stuck-active {
  background-color: #0d6efd; /* 固定時は青色に */
  color: #fff;
  /* 影を出して浮き上がらせる */
  box-shadow: 0 4px 10px rgba(0,0,0,0.2); 
}

/* ダミーの空白要素 */
.dummy-js-content {
  padding: 20px;
  color: #6c757d;
  height: 500px;
  background-image: repeating-linear-gradient(0deg, transparent, transparent 19px, #f1f3f5 19px, #f1f3f5 20px);
}

/* コードエリア */
.stk-js-code-area {
  background-color: #282c34;
  color: #abb2bf;
  padding: 20px;
  border-radius: 6px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  border-left: 4px solid #0d6efd;
  overflow-x: auto;
  text-align: left;
}
JavaScriptコード表示
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const sentinel = document.getElementById('js-sentinel');
    const header = document.getElementById('js-sticky-header');
    
    if (sentinel && header) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (!entry.isIntersecting) {
            // 画面外に出た=固定された
            header.classList.add('is-stuck-active');
          } else {
            // 画面内に戻った=固定解除
            header.classList.remove('is-stuck-active');
          }
        });
      });
      observer.observe(sentinel);
    }
  });
</script>

まとめ

分かりやすいようにまとめを記載します。

本記事のまとめ
  • position: stickyは親要素の範囲内でのみ追従し、親の終端に到達すると固定が解除される。
  • 固定を機能させるには topbottomleftrightのいずれかの閾値(座標)指定が必須である。
  • スクロール時に他の要素の下敷きになるのを防ぐため、原則としてz-indexを併用する。
  • FlexboxやGrid環境下ではalign-self: start等を指定して要素の自動引き伸ばし(stretch)を解除しないと稼働しない。
  • 複数のsticky要素を段差で重ねて固定する場合、手前の要素のサイズ(高さや幅)の分だけ座標数値をズラして指定する。
  • テーブルの列を複数固定する場合、手前の列幅をwidth等で固定し、数値を次の列のleftに指定する。
  • テーブルのヘッダー固定は<tr>ではなく<th>などのセル要素へ直接指定する。
  • 固定したテーブルセルは背景色を指定し、枠線が消える仕様にはbox-shadowで代替して対応する。
  • 先祖要素のどこかにoverflow: hidden(またはauto,clip)が存在すると、stickyは無効化される。
  • iOS Safariに対応するため、標準のposition: sticky;の直前に-webkit-stickyを記述する。
  • 固定された瞬間のスタイル変更はIntersectionObserverとダミー要素を用いたJS連携で検知する。

よくある質問(FAQ)

position: stickyを指定したのに全く効かない(固定されない)のはなぜですか?

実務で多い原因は、親要素やさらに上の先祖要素のどこかにoverflow: hidden(またはauto,clip)が指定されていることです。

CSSの仕様上、先祖にこれらの指定があるとstickyは機能停止します。

また、固定を開始する位置であるtop: 0;bottom: 0;などの「座標指定」を書き忘れている場合もただの通常配置となってしまうため、まずはこの2点をデベロッパーツールで確認してください。

stickyとfixedの違いは何ですか?

要素が固定される「範囲」が決定的に異なります。

fixedは「ブラウザの画面全体」を基準にして固定されるため、ページの一番下までずっとついてきます。

一方stickyは「直近の親要素の範囲内」でのみ固定されます。

そのため、親要素のコンテンツが終わると、固定が解除されて一緒に画面外へとスクロールされていくのが特徴です。

ヘッダーにはfixed、サイドバーや目次にはstickyが使われます。

ヘッダーをstickyで上部固定したら、スクロール時に下の画像が透けて被さってきます。

z-indexの指定が不足しています。

ヘッダーの下にくるコンテンツにposition: relative;やアニメーションなどのスタイルがかかっていると、重なりの階層がヘッダーよりも上になってしまいます。

ヘッダーを上部固定する際は、position: sticky; top: 0; z-index: 100;のように十分大きなz-indexをセットで指定して最前面を維持してください。

テーブルのヘッダー行にstickyをかけても固定されません。

テーブルのレンダリング仕様による問題です。

行要素である<tr><thead>に対して直接position: sticky;を指定しても、多くのブラウザではうまく固定されず一緒に流れてしまいます。

テーブルの見出しを固定したい場合は、行ではなく、その中にある見出しセル<th>要素それぞれに対して直接position: sticky;top: 0;を指定してください。

PCのChromeでは綺麗に固定されるのに、iPhone(Safari)で見ると効いていません。

Safari向けの「ベンダープレフィックス」が不足しています。
一部のiOS Safariや古いブラウザ環境では、標準のプロパティ名だけでは認識されません。

解決するには、CSSの記述を以下のように2行に分けて書いてください。

position: -webkit-sticky; /* 先にSafari用を書く */
position: sticky;         /* 次に標準仕様を書く */

順番を逆にするとモダンブラウザで不具合が起きる可能性があるため、-webkit-を先に書くのがポイントです。

CONTACT

サイト制作でお困りの人はお気軽にご連絡ください。
どんなお悩み事も丁寧に返信させて頂きます。

WordPress移行
Webサイトを公開しよう!

「どのサーバーを選べばいいか分からない…」そんな悩みを解決!
WordPressデビューに最適なサーバーを徹底比較しました。

この記事を書いた人

sugiのアバター sugi Site operator

【経歴】玉川大学工学部卒業→新卒SIer企業入社→2年半後に独立→プログラミングスクール運営/受託案件→フリーランスエンジニア&SEOコンサル→Python特化のコンテンツサイトJob Code&UIコピペサイトCode Stock運営中

目次
HTML/CSSで自由自在!自分のブログを始めよう
ブログ向けサーバーを見る