WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

【2026年版】position: stickyで追従させる方法|ヘッダー・目次・表を固定

CSSで「スクロールしてもヘッダーが追いかけてくる」「目次が画面に固定されている」という動きを実装したいとき、position: sticky が最もシンプルかつ強力な選択肢です。JavaScriptは不要で、数行のCSSだけで追従UIを作れます。かつては JavaScript のスクロールイベント監視でしか実現できなかったこの動きが、今や純粋なCSSプロパティ一行で実装できる時代になりました。

しかし「書いたのに動かない」「なぜか固定されない」というトラブルに直面するケースも多く、原因を知らないまま試行錯誤で時間を溶かしてしまいがちです。position: sticky は設定が簡単な一方で、「親要素の overflow」「高さの関係」「display プロパティとの干渉」など、複数の落とし穴が存在します。これらを知らずに書いても動かないのは当然で、仕組みを理解してから書くことが重要です。

本記事では、position: sticky の仕組みから、ヘッダー・目次・テーブルへの応用、そして「効かない」原因と対処法まで、コピペで使えるコードとともに網羅的に解説します。初心者の方は基本から順番に読み進めてください。経験者の方は目次から「効かない原因と対処」セクションに直接ジャンプしていただいても構いません。

先に、この記事の結論をまとめます。

  • position: stickytop(または bottom)を必ず指定しないと動かない
  • 親要素に overflow: hidden / auto / scroll が設定されていると sticky が無効になる
  • 親要素の高さが子要素より大きくないと sticky が効く「余地」がない
  • ヘッダー・サイドバー目次・テーブルのthead固定など、幅広い場面でJavaScript不要で実現できる
  • 「効かない」パターンは7つに集約でき、それぞれ明確な対処法がある
目次

position: stickyとは?fixed / absolute との違い

position: sticky は「通常フローの中に配置されつつ、スクロール位置に応じて画面に固定される」という、relativefixed の中間的な挙動をするプロパティです。スクロールする前は通常のブロック要素として機能し、指定した閾値(top 値など)に達した瞬間から固定表示に切り替わります。ページ上部から要素が流れてきて、画面上端に届いたタイミングで「張り付く」イメージです。

他の position 値との違いを整理すると次の通りです。

プロパティ通常フロー基準点スクロールとの関係用途例
static(初期値)維持するなし一緒に動く通常のコンテンツ配置
relative維持する自分自身一緒に動くz-indexや子のabsoluteの基準
absolute外れる直近の非static祖先一緒に動くポップアップ・バッジ配置
fixed外れるビューポート常に固定モーダル・チャットボタン
sticky維持するスクロールコンテナ閾値まで動く・以降固定追従ヘッダー・目次・thead固定

fixed はビューポートを基準に常時固定されるため、ページの他の要素と重なりやすく、レイアウト崩れへの対処が必要になることがあります。また fixed は通常フローから外れるため、ページに「隙間」ができないよう padding などで調整する手間が発生します。一方 sticky は親要素の範囲内でだけ固定されるため、親のコンテンツが終わると自然に流れていきます。通常フロー内に留まるので他の要素との重なりも制御しやすいです。これがサイドバー目次やthead固定に適している理由です。

sticky の「スクロールコンテナ」とは

sticky が固定される基準は「スクロールコンテナ」です。スクロールコンテナとは、overflow: scroll または overflow: auto が設定されている最も近い祖先要素のことです。そのような祖先がなければ、ビューポート(ブラウザの表示領域そのもの)がスクロールコンテナになります。body 全体をスクロールする一般的なページでは、ビューポートが基準になります。特定の div 内でスクロールしているケースでは、その div がスクロールコンテナになります。この違いを理解していないと、「書いたはずなのに動かない」という事態に陥りやすいので注意してください。

なお、sticky 要素はスクロールコンテナ内でスクロールされるコンテンツの一部でなければなりません。スクロールコンテナ自体の直接の子要素でない場合や、スクロールコンテナがビューポートと同じ大きさの場合など、条件が揃わないと sticky は無効になります。「なぜ動かないのか」と迷ったときは、まずスクロールコンテナがどこにあるかを確認する習慣をつけましょう。

基本の使い方(top指定と「親要素の高さ・スクロール範囲」の関係)

position: sticky の最もシンプルな書き方は以下の通りです。top の値は「スクロールコンテナの上端からどの距離まで来たら固定するか」を意味します。要素がスクロールによって画面上端から top の値以下の位置まで到達すると、その場所に固定されます。

.sticky-element {
  position: sticky;
  top: 0; /* 画面上端に到達したら固定 */
}

top: 0 は画面上端ピッタリで固定します。サイトにグローバルヘッダーがある場合は、そのヘッダーの高さ分だけ余白をあけます。例えばヘッダーが高さ 60px なら top: 60px とします。ここを合わせないとヘッダーの下に隠れてしまうため、実際の案件では必ずヘッダー高さに合わせた top 値を指定してください。

.sticky-element {
  position: sticky;
  top: 60px; /* 60pxのヘッダー分だけ下げる */
}

親要素の高さが sticky の「有効範囲」を決める

sticky 要素は「親要素の高さ」の範囲内でしか固定されません。親要素のコンテンツが終わると、sticky 要素もそれ以上固定されずに上に流れていきます。例えばサイドバーの目次を sticky にする場合、その目次の親であるサイドバー div の高さが記事本文より短いと、目次はすぐに固定が外れてしまいます。これは特にサイドバー目次を実装する際に多く引っかかる落とし穴です。

この問題を回避するには、sticky 要素の親(またはその上位の要素)を、スクロール範囲全体にわたって十分な高さにする必要があります。Flexbox や Grid レイアウトを使っている場合、align-self: stretchalign-items: stretch で高さを引き伸ばすか、意図的に align-self: start を設定して sticky の有効範囲を確保するといった対処が必要になります。

<div class="sidebar">
  <!-- この div の高さが sticky の有効範囲を決める -->
  <nav class="toc">
    <ul>
      <li><a href="#h2-1">見出し1</a></li>
      <li><a href="#h2-2">見出し2</a></li>
    </ul>
  </nav>
</div>
.sidebar {
  /* 親の高さを明示または align-self: stretch などで引き延ばす */
  align-self: stretch; /* Flexboxレイアウト時に有効 */
}

.toc {
  position: sticky;
  top: 80px;
}

bottom / left / right での固定

sticky は top だけでなく bottomleftright でも使えます。bottom: 0 にすると、要素が画面下端に到達したら固定されます。ページ下部のCTAバナーや「ページトップへ」ボタンを sticky で実現したい場合に有効です。横スクロールのテーブルで列を固定したい場合は left: 0right: 0 を使います。実際には top を使うケースが圧倒的に多いですが、これらの組み合わせも覚えておくと応用が広がります。

/* 画面下端に追従させる場合 */
.sticky-footer-bar {
  position: sticky;
  bottom: 0;
}

/* テーブルの左端列を固定する場合 */
.table-first-col {
  position: sticky;
  left: 0;
  background: #fff; /* 背景色を指定しないと透けて見える */
}

追従ヘッダー(ページ上部にヘッダーを固定)実装

サイトのグローバルナビゲーションをスクロールしても画面上部に表示し続けるのが「追従ヘッダー」です。ユーザーがページのどこにいても、ナビゲーションへすぐアクセスできるためUXが向上します。position: sticky を使えば、JavaScriptなしでシンプルに実現できます。ただし sticky ヘッダーはコンテンツの表示面積を常に減らすため、高さは必要最小限に抑えることが重要です。特にモバイルでは画面の縦幅が限られているため、スマートフォンでは細め(50px 前後)のヘッダーにする設計が一般的です。

最小構成のHTMLとCSS

<header class="site-header">
  <div class="header-inner">
    <a href="/" class="logo">サイト名</a>
    <nav class="global-nav">
      <ul>
        <li><a href="/about/">About</a></li>
        <li><a href="/works/">Works</a></li>
        <li><a href="/contact/">Contact</a></li>
      </ul>
    </nav>
  </div>
</header>

<main>
  <!-- ページ本文 -->
</main>
.site-header {
  position: sticky;
  top: 0;
  z-index: 100; /* コンテンツより手前に表示 */
  background: #fff;
  border-bottom: 1px solid #e0e0e0;
  width: 100%;
}

.header-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 16px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.global-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 24px;
}

.global-nav a {
  text-decoration: none;
  color: #333;
  font-size: 0.9rem;
}

ポイントは z-index: 100background: #fff の2つです。z-index を設定しないとコンテンツがヘッダーの上に重なる場合があります。また background-color を設定しないと、ヘッダーが半透明になってスクロール時にコンテンツが透けて見えてしまいます。必ず両方セットで設定してください。

スクロール時に影をつけてヘッダーを強調する

スクロールが始まるとヘッダーに影が付くようにすると、コンテンツとの境界が明確になりUXが向上します。ページ最上部ではヘッダーとページ背景が一体に見え、スクロールし始めると影が現れてヘッダーが「浮き上がる」ような演出になります。JavaScriptでスクロールイベントを検知してクラスを付け外しする方法が一般的です。CSSの position: sticky だけでは「固定されたかどうか」を検知できないため、この部分だけはJavaScriptを使います。

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #fff;
  transition: box-shadow 0.3s ease;
}

/* JavaScriptで付与するクラス */
.site-header.is-scrolled {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
<script>
const header = document.querySelector('.site-header');
window.addEventListener('scroll', () => {
  if (window.scrollY > 0) {
    header.classList.add('is-scrolled');
  } else {
    header.classList.remove('is-scrolled');
  }
});
</script>

スマートフォン対応(メニューボタン付き)

スマートフォンではナビゲーションをハンバーガーメニューに切り替えるのが一般的です。ヘッダーは引き続き sticky で固定します。PC用のナビをメディアクエリで非表示にし、代わりにハンバーガーボタンを表示します。ボタンクリックでメニューを開閉する動きはJavaScriptが必要ですが、ヘッダーの追従自体はCSSのみで動きます。

@media (max-width: 640px) {
  .global-nav {
    display: none; /* PC用ナビを非表示 */
  }

  .menu-toggle {
    display: block; /* ハンバーガーボタンを表示 */
  }

  /* メニューopen時 */
  .global-nav.is-open {
    display: block;
    position: fixed;
    top: 60px;
    left: 0;
    width: 100%;
    background: #fff;
    padding: 16px;
    border-top: 1px solid #e0e0e0;
    z-index: 99;
  }

  .global-nav.is-open ul {
    flex-direction: column;
    gap: 16px;
  }
}

サイドの目次(もくじ)を追従させる実装

ブログ記事やドキュメントページでよく見かける「サイドバーの目次がスクロールに追いかけてくる」動きも、position: sticky で実装できます。2カラムレイアウトが前提になります。左側に記事本文、右側にサイドバー目次を配置し、サイドバー内の目次に sticky を設定します。記事本文が長いほどstickyが有効な区間が長くなり、快適に使える目次になります。

ただし、この実装では親コンテナの設定が重要です。サイドバーの親要素(2カラムを束ねるコンテナ)が、記事本文と同じ高さになるように設定しないと、サイドバーの sticky が早々に解除されてしまいます。Flexbox の場合、align-items: flex-start を設定することが鍵になります。デフォルトの align-items: stretch だと、サイドバーが本文と同じ高さに引き伸ばされて sticky が効かなくなるわけではありませんが、記事本文コンテナの高さと整合させるために flex-start を指定しておくのが安全です。

2カラムレイアウトの基本構造

<div class="page-layout">

  <main class="main-content">
    <h1>記事タイトル</h1>
    <p>本文の内容...(ここが長いと sticky が効く範囲が広がる)</p>
    <h2 id="section-1">見出し1</h2>
    <p>...</p>
    <h2 id="section-2">見出し2</h2>
    <p>...</p>
  </main>

  <aside class="sidebar">
    <nav class="toc">
      <h3>目次</h3>
      <ul>
        <li><a href="#section-1">見出し1</a></li>
        <li><a href="#section-2">見出し2</a></li>
      </ul>
    </nav>
  </aside>

</div>
.page-layout {
  display: flex;
  gap: 40px;
  align-items: flex-start; /* これが重要! */
  max-width: 1100px;
  margin: 0 auto;
  padding: 40px 16px;
}

.main-content {
  flex: 1;
  min-width: 0; /* テキストのはみ出し防止 */
}

.sidebar {
  width: 280px;
  flex-shrink: 0;
}

.toc {
  position: sticky;
  top: 80px; /* ヘッダーの高さ + 余白 */
}

目次のスタイリング

目次のビジュアルを整えると、記事全体の読みやすさが向上します。カード風のデザインにして記事本文と視覚的に区別するのが一般的です。左ボーダーでアクセントカラーを添えるとブランドの統一感が出ます。

.toc {
  position: sticky;
  top: 80px;
  background: #f8f8f8;
  border-radius: 8px;
  padding: 20px;
  border-left: 4px solid #d16176;
}

.toc h3 {
  font-size: 0.85rem;
  color: #888;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 0 0 12px;
}

.toc ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.toc li {
  padding: 4px 0;
}

.toc a {
  text-decoration: none;
  color: #333;
  font-size: 0.9rem;
  line-height: 1.6;
  transition: color 0.2s;
}

.toc a:hover {
  color: #d16176;
}

スクロールに応じて現在地の目次項目をハイライトする

Intersection Observer API を使うと、現在画面に表示されている見出しに対応する目次項目を自動でハイライトできます。ユーザーが記事のどこを読んでいるかが一目でわかるようになります。スクロールイベントで監視する従来の方法より、Intersection Observer は処理が軽く実装もシンプルです。rootMargin の値で「どの位置に来たらアクティブとみなすか」を調整できます。

<script>
const headings = document.querySelectorAll('h2[id], h3[id]');
const tocLinks = document.querySelectorAll('.toc a');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      tocLinks.forEach(link => link.classList.remove('is-active'));
      const activeLink = document.querySelector(`.toc a[href="#${entry.target.id}"]`);
      if (activeLink) activeLink.classList.add('is-active');
    }
  });
}, { rootMargin: '-80px 0px -60% 0px' });

headings.forEach(h => observer.observe(h));
</script>
.toc a.is-active {
  color: #d16176;
  font-weight: bold;
}

テーブルの見出し行(thead)を固定する

行数の多いデータテーブルでは、スクロールするとヘッダー行が見えなくなって「この列は何のデータだったか」がわからなくなります。thead th に sticky を適用すると、テーブルを縦スクロールしてもヘッダー行が画面上部に固定されます。Excelの「先頭行を固定」と同じ動きをCSSだけで実現できます。

ただし、テーブルでの sticky には注意点があります。theadtr そのものに sticky を適用しても効かないブラウザがあります。必ず th(または td)に適用するのが正しい方法です。また、背景色の指定を忘れると、スクロール時にtbodyのコンテンツがtheadの下に透けて見えてしまいます。この2点は特に注意してください。

基本実装

<div class="table-wrapper">
  <table class="data-table">
    <thead>
      <tr>
        <th>商品名</th>
        <th>価格</th>
        <th>在庫</th>
        <th>カテゴリ</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>商品A</td>
        <td>¥1,000</td>
        <td>50</td>
        <td>食品</td>
      </tr>
      <!-- 行が続く -->
    </tbody>
  </table>
</div>
.table-wrapper {
  max-height: 400px; /* テーブルのスクロール範囲を制限 */
  overflow-y: auto;  /* 縦スクロールを有効化 */
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table thead th {
  position: sticky;
  top: 0;
  background: #51322b;  /* 背景色は必須 */
  color: #fff;
  padding: 12px 16px;
  text-align: left;
  z-index: 1;
}

.data-table tbody td {
  padding: 10px 16px;
  border-bottom: 1px solid #e0e0e0;
}

.data-table tbody tr:hover {
  background: #fdf0f0;
}

ページ全体でスクロールするテーブル(table-wrapper なし)

テーブルをページ全体のスクロールで使う場合(table-wrapper に overflow を設定しない場合)でも、thead th に sticky を適用できます。この場合、スクロールコンテナはビューポートになります。top の値をグローバルヘッダーの高さに合わせることが重要で、ここがずれるとヘッダーの下にtheadが隠れてしまいます。

/* ページ全体スクロールの場合 */
.data-table thead th {
  position: sticky;
  top: 60px; /* グローバルヘッダーが60pxの場合 */
  background: #fff;
  z-index: 10;
  border-bottom: 2px solid #d16176;
}

左端の列を固定する(横スクロールのテーブル)

横方向に列数が多いテーブルでは、左端の「項目名」列を固定したいケースがあります。position: stickyleft: 0 の組み合わせで実現できます。縦スクロールのthead固定と横スクロールの列固定を同時に使うこともでき、その場合は z-index の管理が重要になります。

.table-wrapper {
  overflow-x: auto; /* 横スクロールを有効化 */
}

/* 先頭列(th・td どちらも)を固定 */
.data-table th:first-child,
.data-table td:first-child {
  position: sticky;
  left: 0;
  background: #fff;
  z-index: 1;
  box-shadow: 2px 0 4px rgba(0, 0, 0, 0.08);
}

/* ヘッダー行かつ先頭列 */
.data-table thead th:first-child {
  z-index: 2; /* 他のstickyより手前に */
  background: #51322b;
}

position: stickyが効かない7つの原因と対処

「position: sticky を書いたのに固定されない」というトラブルには、ほぼ必ず原因があります。「ブラウザが対応していないのかも」と疑う前に、以下の7つのチェックリストを上から順番に確認してください。ほとんどのケースはここで解決します。

原因1:top / bottom / left / right を指定していない

position: sticky は topbottomleftright のいずれかを必ず指定する必要があります。指定しないと sticky は有効になりません。これは最も多いミスです。「固定したい」という意図だけで position: sticky と書いてしまい、top を忘れるケースが非常に多いので、セットで覚えてください。

/* NG:topなしは動かない */
.sticky {
  position: sticky;
}

/* OK:topを必ず指定する */
.sticky {
  position: sticky;
  top: 0;
}

原因2:親要素に overflow: hidden / auto / scroll が設定されている

sticky 要素の祖先要素のいずれかに overflow: hiddenoverflow: autooverflow: scroll が設定されていると、その要素がスクロールコンテナになります。sticky はそのコンテナを基準に動こうとしますが、多くの場合そのコンテナはビューポートより小さく、sticky が意図通りに動きません。これは2番目に多い原因です。

特に「clearfix のために overflow: hidden を書いていた」「横スクロールを防ぐために書いていた」というケースで起きやすいです。デベロッパーツールで親要素を一つずつ確認して、overflow が設定されていないかチェックしてください。

/* 原因:どこかの祖先にこれがある */
.wrapper {
  overflow: hidden; /* ← これが sticky を殺す */
}

/* 対処:overflow:hidden を削除するか、visible に変える */
.wrapper {
  overflow: visible;
}

/* 横はみ出しだけ隠したい場合は overflow-x: clip を使う */
.wrapper {
  overflow-x: clip; /* 縦方向のstickyには影響しにくい */
}

原因3:親要素の高さが sticky 要素より大きくない

sticky は親要素の範囲内でのみ固定されます。親要素が sticky 要素と同じ高さしかない場合、固定できる「余地」がゼロのため動きません。例えば親が高さ固定の小さい div で、その中に sticky を入れている場合がこれに該当します。「画面内に収まる小さいコンテナ」に sticky を入れても意味がないので、構造を見直す必要があります。

/* NG:親が100px、子も100px → 動く余地がない */
.parent {
  height: 100px;
}
.child {
  position: sticky;
  top: 0;
  height: 100px;
}

/* OK:親を大きくするか、高さを height: auto にする */
.parent {
  height: auto; /* コンテンツに応じて伸びる */
}

原因4:display: flex の align-items が stretch になっている

Flexbox のコンテナで align-items: stretch(デフォルト値)が設定されている場合、flex アイテムの高さがコンテナ全体と同じ高さに引き伸ばされます。このとき sticky 要素の高さと親の高さが同じになり、固定できる余地がなくなってしまいます。

/* 問題が出るパターン */
.flex-container {
  display: flex;
  align-items: stretch; /* デフォルト値 */
}

/* sticky を使う側のコンテナは flex-start を推奨 */
.flex-container {
  display: flex;
  align-items: flex-start;
}

原因5:display: grid で align-self が stretch になっている

Grid レイアウトでも同様の問題が起きます。グリッドアイテムはデフォルトで高さが行全体に引き延ばされます(align-self: stretch)。sticky を使いたいグリッドアイテムには align-self: start を設定して、自身のコンテンツの高さに留まるようにします。

/* sticky を入れるグリッドアイテム自身に align-self を設定 */
.sidebar {
  align-self: start; /* stretch から start に変える */
}

.toc {
  position: sticky;
  top: 80px;
}

原因6:テーブルで thead や tr に適用している

テーブルの theadtr に sticky を直接適用しようとしても、ブラウザによっては display: table-header-group などの内部 display が上書きされて効かないことがあります。sticky を効かせるには th または td に適用するのが正しい方法です。要素の階層を一段下げるだけで動くようになります。

/* NG:thead に sticky は効かないブラウザがある */
thead {
  position: sticky;
  top: 0;
}

/* OK:th に適用する */
thead th {
  position: sticky;
  top: 0;
  background: #fff;
}

原因7:z-index が設定されておらずコンテンツの下に隠れる

sticky 自体は動いているが、スクロール時に他のコンテンツの下に潜り込んでしまう問題です。sticky 要素は z-index を設定しないと重なり順が不定になります。ヘッダーなど前面に表示したい要素には明示的に z-index を設定してください。また、背景色が透明だとコンテンツが透けて見えるため、background-color も合わせて設定します。

.site-header {
  position: sticky;
  top: 0;
  z-index: 100; /* コンテンツより大きな値を指定 */
  background: #fff; /* 背景色も必須 */
}

よくある質問(FAQ)

Q1. position: sticky はどのブラウザに対応していますか?

現在のモダンブラウザでは全て対応しています。Chrome・Firefox・Safari・Edge のいずれも問題なく動作します。Safari は過去に -webkit-sticky プレフィックスが必要でしたが、現在(Safari 13以降)はプレフィックスなしで動作します。Internet Explorer 11 は非対応ですが、2022年6月に IE11 のサポートが終了しているため、現在のWeb開発では実質的に全ブラウザ対応と考えて問題ありません。古いブラウザへの対応が必要な案件でのみ、JavaScriptによるフォールバックを検討してください。

/* 念のためプレフィックス付きも書く場合 */
.sticky-element {
  position: -webkit-sticky; /* Safari 対応(念のため) */
  position: sticky;
  top: 0;
}

Q2. position: fixed との使い分けはどうすれば良いですか?

「常にビューポートに固定したい」なら fixed、「コンテンツの流れの中で、一定位置に来たら固定したい」なら sticky という使い分けが基本です。モーダルダイアログや画面右下のチャットボタンなど、ページ上のどこからでも常にアクセスできる必要があるUIは fixed が適切です。記事のナビゲーション目次や記事ヘッダーなど、特定のコンテンツ領域内だけに関連するUIは sticky が適切です。sticky はレイアウトフロー内に残るためレイアウト崩れが起きにくく、スペースの確保も自動なのでメンテナンスが楽です。実案件では、サイト全体のヘッダーでも sticky を採用するケースが増えており、fixed より扱いやすいと評価されています。

Q3. stickyヘッダーがアンカーリンクのジャンプ先を隠してしまいます。解決方法は?

sticky ヘッダーがあると、アンカーリンクでジャンプしたとき見出しがヘッダーの下に隠れてしまうことがあります。これは非常によく起きる問題です。対処法は scroll-margin-top または scroll-padding-top を使うことです。scroll-margin-top はジャンプ先の要素に設定し、scroll-padding-top は html 要素に設定してサイト全体に一括適用できます。どちらもヘッダーの高さ分だけジャンプ位置を下にずらします。

/* 方法1:ジャンプ先の見出しに設定 */
h2, h3 {
  scroll-margin-top: 80px; /* ヘッダーの高さ分だけ余白をとる */
}

/* 方法2:html 要素に設定(全アンカーに一括適用) */
html {
  scroll-padding-top: 80px;
}

Q4. テーブルのtheadをstickyにすると線が消えたり二重になったりします。なぜですか?

border-collapse: collapse を使っているテーブルで thead th に sticky を適用すると、ボーダーが消えたり二重表示になったりする問題がブラウザによって発生します。これは border-collapse: collapse が sticky と相性が悪いためです。対処法は border-collapse: separate に切り替えて border-spacing: 0 と組み合わせる方法、またはボーダーではなく box-shadow で線を表現する方法の2つがあります。

/* 対処法1:border-collapse を separate に */
.data-table {
  border-collapse: separate;
  border-spacing: 0;
}

.data-table thead th {
  position: sticky;
  top: 0;
  background: #fff;
  border-bottom: 2px solid #d16176;
}

/* 対処法2:box-shadowでボーダーを擬似的に再現 */
.data-table thead th {
  position: sticky;
  top: 0;
  background: #fff;
  box-shadow: inset 0 -2px 0 #d16176;
}

Q5. CSSカスタムプロパティを使ってヘッダーの高さを一元管理する方法はありますか?

ヘッダーの高さが変わるたびに top: 60px をあちこちで書き直すのは非効率です。CSS カスタムプロパティ(変数)を使えば一元管理できます。:root に変数を定義し、sticky の top 値やアンカージャンプの scroll-margin-top で参照します。ヘッダーの高さを変更するときは変数を一か所書き換えるだけで済むため、保守性が大幅に向上します。スマートフォンとPCでヘッダー高さが異なる場合も、メディアクエリ内で変数を上書きするだけで全ての値が自動で更新されます。

:root {
  --header-height: 60px;
}

@media (max-width: 640px) {
  :root {
    --header-height: 50px; /* スマホではヘッダーを小さく */
  }
}

.site-header {
  position: sticky;
  top: 0;
  height: var(--header-height);
  z-index: 100;
}

.toc {
  position: sticky;
  top: calc(var(--header-height) + 16px); /* ヘッダー高さ + 余白 */
}

h2, h3 {
  scroll-margin-top: calc(var(--header-height) + 8px);
}

html {
  scroll-padding-top: var(--header-height);
}

関連記事

CSS・Webデザイン実装の総まとめ

CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

WithCodeを体験できる初級コース公開中!

WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次