アンカーナビ Anchor Nav

ページ内のセクションへスムーズスクロールするナビゲーション。

使用場面: ランディングページやドキュメントページでセクション間を移動 採用例: MDN Web Docs, Stripe Docs

ライブデモ

はじめに

アンカーナビゲーションは長いページ内のセクションにスムーズにジャンプするためのUIパターンです。技術ドキュメント、利用規約、FAQページなどで広く採用されています。ユーザーはページ全体をスクロールすることなく、目的の情報に素早くアクセスできます。目次として機能するナビゲーションリンクは、ページの構造を一目で把握する手助けにもなります。特にモバイルデバイスでは、長いページをスクロールする負担を大幅に軽減できるため、ユーザー体験の向上に直結します。

インストールと設定

アンカーナビの実装はシンプルです。各セクションにユニークなIDを付与し、ナビゲーションのリンクを対応するIDにhref属性で紐づけます。scroll-behavior: smooth をHTML要素に設定すれば、クリック時にスムーズスクロールが実現します。IntersectionObserver APIを使うと、現在表示中のセクションに対応するナビリンクをハイライトすることができます。これにより、ユーザーは自分がページのどの位置にいるかを視覚的に把握できるようになります。閾値(threshold)の設定により、ハイライトの切り替わりタイミングを微調整できます。

基本的な使い方

最もシンプルな実装では、ページ上部にリンクリストを配置し、各リンクがページ内のセクションIDを指します。よりリッチな実装では、サイドバーに固定されたナビゲーションを配置し、スクロールに応じて現在のセクションをハイライトします。ハイライトにはCSSのクラス切り替えを用い、背景色やボーダー、フォントウェイトなどで視覚的なフィードバックを提供します。

カスタマイズ

ナビゲーションのデザインはプロジェクトのデザインシステムに合わせてカスタマイズできます。インデントを使った階層表示、アイコンの付与、進捗インジケーターの追加など、さまざまなバリエーションが考えられます。スクロールスパイのオフセット値を調整することで、固定ヘッダーの高さを考慮した正確なハイライトが可能になります。また、アニメーションを加えることで、ハイライトの切り替わりをよりスムーズに見せることができます。

アクセシビリティ

アンカーナビゲーションにはnav要素とaria-label属性を使用し、スクリーンリーダーにとっても理解しやすい構造にします。各リンクには明確なテキストを設定し、現在地を示すためにaria-current属性の使用を検討します。キーボードユーザーがTabキーでナビゲーション内を移動し、Enterキーで対象セクションにジャンプできることを確認してください。フォーカス管理も重要で、ジャンプ先のセクション見出しにフォーカスを移動させる実装が理想的です。

実装のコツと注意点

URLのハッシュフラグメントを更新することで、ブラウザの履歴と連携させることができます。ただし、history.replaceStateを使って履歴エントリの過剰な追加を防ぎましょう。固定ヘッダーがある場合は、scroll-margin-topプロパティで各セクションのスクロール位置を調整します。長いページではナビゲーション自体もスクロール可能にする必要があるかもしれません。パフォーマンスの観点では、IntersectionObserverは軽量ですが、セクション数が非常に多い場合はデバウンスを検討してください。

ソースコード

<div class="demo-anchor-wrap" style="display:flex;gap:16px;max-height:500px;overflow-y:auto;border:1px solid #e5e7eb;border-radius:10px;" id="demo-anchor-scroll"><nav class="demo-anchor-nav" style="width:180px;flex-shrink:0;position:sticky;top:0;align-self:flex-start;padding:12px 0;border-right:1px solid #e5e7eb;"><a class="demo-anchor-link" href="#demo-anc-intro" data-target="demo-anc-intro" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">はじめに</a><a class="demo-anchor-link" href="#demo-anc-install" data-target="demo-anc-install" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">インストールと設定</a><a class="demo-anchor-link" href="#demo-anc-usage" data-target="demo-anc-usage" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">基本的な使い方</a><a class="demo-anchor-link" href="#demo-anc-customize" data-target="demo-anc-customize" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">カスタマイズ</a><a class="demo-anchor-link" href="#demo-anc-a11y" data-target="demo-anc-a11y" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">アクセシビリティ</a><a class="demo-anchor-link" href="#demo-anc-tips" data-target="demo-anc-tips" style="display:block;padding:8px 12px;font-size:12px;color:#555;text-decoration:none;border-left:3px solid transparent;transition:all 0.2s;">実装のコツと注意点</a></nav><div class="demo-anchor-content" style="flex:1;min-width:0;padding:4px 20px;"><section id="demo-anc-intro" style="padding:20px 0;border-bottom:1px solid #e5e7eb;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">はじめに</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">アンカーナビゲーションは長いページ内のセクションにスムーズにジャンプするためのUIパターンです。技術ドキュメント、利用規約、FAQページなどで広く採用されています。ユーザーはページ全体をスクロールすることなく、目的の情報に素早くアクセスできます。目次として機能するナビゲーションリンクは、ページの構造を一目で把握する手助けにもなります。特にモバイルデバイスでは、長いページをスクロールする負担を大幅に軽減できるため、ユーザー体験の向上に直結します。</p></section><section id="demo-anc-install" style="padding:20px 0;border-bottom:1px solid #e5e7eb;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">インストールと設定</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">アンカーナビの実装はシンプルです。各セクションにユニークなIDを付与し、ナビゲーションのリンクを対応するIDにhref属性で紐づけます。scroll-behavior: smooth をHTML要素に設定すれば、クリック時にスムーズスクロールが実現します。IntersectionObserver APIを使うと、現在表示中のセクションに対応するナビリンクをハイライトすることができます。これにより、ユーザーは自分がページのどの位置にいるかを視覚的に把握できるようになります。閾値(threshold)の設定により、ハイライトの切り替わりタイミングを微調整できます。</p></section><section id="demo-anc-usage" style="padding:20px 0;border-bottom:1px solid #e5e7eb;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">基本的な使い方</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">最もシンプルな実装では、ページ上部にリンクリストを配置し、各リンクがページ内のセクションIDを指します。よりリッチな実装では、サイドバーに固定されたナビゲーションを配置し、スクロールに応じて現在のセクションをハイライトします。ハイライトにはCSSのクラス切り替えを用い、背景色やボーダー、フォントウェイトなどで視覚的なフィードバックを提供します。</p></section><section id="demo-anc-customize" style="padding:20px 0;border-bottom:1px solid #e5e7eb;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">カスタマイズ</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">ナビゲーションのデザインはプロジェクトのデザインシステムに合わせてカスタマイズできます。インデントを使った階層表示、アイコンの付与、進捗インジケーターの追加など、さまざまなバリエーションが考えられます。スクロールスパイのオフセット値を調整することで、固定ヘッダーの高さを考慮した正確なハイライトが可能になります。また、アニメーションを加えることで、ハイライトの切り替わりをよりスムーズに見せることができます。</p></section><section id="demo-anc-a11y" style="padding:20px 0;border-bottom:1px solid #e5e7eb;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">アクセシビリティ</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">アンカーナビゲーションにはnav要素とaria-label属性を使用し、スクリーンリーダーにとっても理解しやすい構造にします。各リンクには明確なテキストを設定し、現在地を示すためにaria-current属性の使用を検討します。キーボードユーザーがTabキーでナビゲーション内を移動し、Enterキーで対象セクションにジャンプできることを確認してください。フォーカス管理も重要で、ジャンプ先のセクション見出しにフォーカスを移動させる実装が理想的です。</p></section><section id="demo-anc-tips" style="padding:20px 0;"><h3 style="font-size:16px;font-weight:700;color:var(--ui-primary);margin:0 0 10px;">実装のコツと注意点</h3><p style="font-size:13px;color:#555;line-height:1.8;margin:0;">URLのハッシュフラグメントを更新することで、ブラウザの履歴と連携させることができます。ただし、history.replaceStateを使って履歴エントリの過剰な追加を防ぎましょう。固定ヘッダーがある場合は、scroll-margin-topプロパティで各セクションのスクロール位置を調整します。長いページではナビゲーション自体もスクロール可能にする必要があるかもしれません。パフォーマンスの観点では、IntersectionObserverは軽量ですが、セクション数が非常に多い場合はデバウンスを検討してください。</p></section></div></div>
<button class="demo-anchor-fab" id="demo-anchor-fab" aria-label="目次を開く"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></button>
.demo-anchor-wrap { min-height: 500px;
  display: flex; gap: 20px; max-height: 250px;
}
.demo-anchor-nav {
  display: flex; flex-direction: column; gap: 2px;
  position: sticky; top: 0; flex-shrink: 0;
  border-left: 2px solid #e5e7eb; padding-left: 0;
}
.demo-anchor-link {
  padding: 6px 14px; font-size: 13px; text-decoration: none;
  color: #999; border-left: 2px solid transparent; margin-left: -2px;
  transition: all 0.2s; white-space: nowrap;
}
.demo-anchor-link:hover { color: #555; }
.demo-anchor-link.active {
  color: var(--ui-primary); font-weight: 600;
  border-left-color: var(--ui-primary);
}
.demo-anchor-content {
  flex: 1; overflow-y: auto; scroll-behavior: smooth;
  padding-right: 8px;
}
.demo-anchor-section {
  padding: 16px 0; min-height: 120px;
  border-bottom: 1px solid #f3f4f6;
}
.demo-anchor-section:last-child { border-bottom: none; }
.demo-anchor-heading {
  margin: 0 0 8px; font-size: 16px; color: var(--ui-primary);
}
.demo-anchor-section p {
  margin: 0; font-size: 13px; color: #555; line-height: 1.6;
}



}
.demo-anchor-fab svg { pointer-events: none; }
.demo-anchor-nav { background: var(--ui-surface, #fff); }
@media (max-width: 767px) {
  .demo-anchor-wrap {
    flex-direction: column !important;
    max-height: 450px !important;
    overflow-y: auto !important;
    position: relative !important;
  }
  .demo-anchor-nav {
    display: none !important;
    position: absolute !important;
    top: 8px; right: 8px;
    width: 200px;
    z-index: 50;
    border-radius: 12px;
    border: 1px solid #e5e7eb;
    box-shadow: 0 8px 24px rgba(0,0,0,0.15);
    max-height: 280px;
    overflow-y: auto;
    padding: 8px 0 !important;
    flex-direction: column !important;
    border-right: none !important;
  }
  [data-ui-theme="light"] .demo-anchor-nav.is-open { background: #ffffff !important; display: flex !important; }
  [data-ui-theme="dark"] .demo-anchor-nav.is-open { background: #1f1f28 !important; display: flex !important; }
  .demo-anchor-link {
    white-space: normal !important;
    border-bottom: none !important;
    border-left: 3px solid transparent !important;
    padding: 8px 14px !important;
  }
  .demo-anchor-fab {
    display: flex;
    position: absolute !important;
    bottom: 12px; right: 12px;
    z-index: 51;
    width: 44px; height: 44px;
    border-radius: 50%; border: none;
    background: var(--ui-primary); color: #fff;
    cursor: pointer;
    align-items: center; justify-content: center;
    box-shadow: 0 4px 12px rgba(0,0,0,0.2);
  }
  .demo-anchor-content { padding: 12px !important; }
}
@media (min-width: 768px) {
  .demo-anchor-fab { display: none !important; }
}
(function(){
  var scrollRoot = document.getElementById("demo-anchor-scroll");
  var links = document.querySelectorAll(".demo-anchor-link");
  var sections = document.querySelectorAll("#demo-anchor-scroll section[id]");
  if(!scrollRoot || !sections.length) return;
  function setActive(id) {
    links.forEach(function(l){
      var isActive = l.getAttribute("data-target") === id;
      l.style.borderLeftColor = isActive ? "var(--ui-primary)" : "transparent";
      l.style.borderBottomColor = isActive ? "var(--ui-primary)" : "transparent";
      l.style.color = isActive ? "var(--ui-primary)" : "#555";
      l.style.fontWeight = isActive ? "700" : "400";
      l.style.background = isActive ? "var(--ui-primary-light, rgba(37,99,235,0.08))" : "transparent";
    });
  }
  var observer = new IntersectionObserver(function(entries){
    entries.forEach(function(entry){
      if(entry.isIntersecting) setActive(entry.target.id);
    });
  }, { root: scrollRoot, rootMargin: "-10% 0px -80% 0px", threshold: 0 });
  sections.forEach(function(s){ observer.observe(s); });
  // クリックでスムーズスクロール
  links.forEach(function(l){
    l.addEventListener("click", function(e){
      e.preventDefault();
      var target = document.getElementById(l.getAttribute("data-target"));
      if(target) target.scrollIntoView({ behavior: "smooth", block: "start" });
    });
  });
  setActive(sections[0].id);
  /* SP: FABで目次開閉 */
  var ancFab = document.getElementById('demo-anchor-fab');
  var ancNav = document.querySelector('.demo-anchor-nav');
  if(ancFab && ancNav){
    ancFab.addEventListener('click', function(e){ e.stopPropagation();
      var open = ancNav.classList.toggle('is-open');
      ancFab.innerHTML = open ? '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>' : '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>';
    });
    /* 目次リンククリックで閉じる */
    ancNav.querySelectorAll('.demo-anchor-link').forEach(function(l){
      l.addEventListener('click', function(){
        ancNav.classList.remove('is-open');
        ancFab.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>';
      });
    });
    document.addEventListener('click', function(e){
      if(!ancNav.contains(e.target) && e.target !== ancFab && ancNav.classList.contains('is-open')){
        ancNav.classList.remove('is-open');
        ancFab.innerHTML = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>';
      }
    });
  }
})();

AIプロンプト

Basic
アンカーナビゲーションをHTML/CSSで作ってください。サイドにリンク一覧があり、クリックすると対応セクションにスムーズスクロールします。
Custom
以下の仕様でアンカーナビゲーションを実装してください。
- サイドバーに固定表示(sticky)
- スクロールスパイ機能(現在セクションをハイライト)
- スムーズスクロールアニメーション
- プライマリカラー: #2563eb
- 左ボーダーのアクティブインジケーター
- セクションが画面に入ったらフェードイン
Advanced
アクセシビリティ対応のアンカーナビゲーションを実装してください。
- nav要素にaria-labelを設定
- IntersectionObserverでスクロールスパイ実装
- aria-current でアクティブリンクを通知
- キーボードナビゲーション対応
- prefers-reduced-motion でスクロールアニメーション無効化
- レスポンシブ(モバイルでは上部にドロップダウンで表示)
- URLハッシュ同期とディープリンク対応
- 見出しの階層構造を反映したインデント表示