スクロールスパイ ナビゲーションは、現在ビューポートに表示されているページのセクションに対応するナビゲーション リンクを自動的にハイライト表示する一般的な UI パターンです。これにより、ユーザーは長いドキュメントをスクロールする際に、自分がどこにいるかを明確に把握できます。
このパターンは、次の 2 つのシナリオでよく使用されます。
- 単一ページのウェブサイト: メインのサイト ナビゲーションが、個別の URL ではなく、単一ページの異なるセクションにリンクしているウェブサイト。
- 長文コンテンツ: 目次やサイドバー ナビゲーションで、記事、ガイド、ドキュメントのユーザーの進行状況を追跡します。
導入方法
従来、スクロールスパイ パターンには JavaScript と IntersectionObserver API が必要でした。最新の CSS では、scroll-target-group プロパティと :target-current 疑似クラスを使用して、これをネイティブに実現できます。
- ナビゲーション リンクでフラグメント識別子が使用されていることを確認する: ナビゲーションは、コンテンツ セクションの
idと一致するフラグメント識別子(例:<a href="#section-1">)を使用して、同じページ上のセクションを指すアンカーリンク(<a>)で構成されている必要があります。 - スクロール ターゲットのグループ化を有効にする: ナビゲーション リンクのコンテナに
scroll-target-group: autoプロパティを適用します。これにより、ブラウザはこれらのリンクを、それぞれのターゲットを追跡するマーカーとして扱うようになります。 - アクティブなリンクのスタイルを設定する: ナビゲーション リンクで
:target-current疑似クラスを使用して、ターゲット セクションがスクロール コンテナに表示されているときにスタイルを適用します。 - セマンティック状態: スクリプトを使用して、スクロールが停止したときにアクティブなリンクの
aria-current="true"を更新し、状態が支援技術に正しく伝達されるようにします。
コード例
この例では、ユーザーがセクションをスクロールすると、固定ナビゲーション メニューのアクティブなリンクがハイライト表示されます。
/* MANDATORY: The container of markers must have scroll-target-group: auto */
nav {
/* Enables automatic tracking of fragment links */
scroll-target-group: auto;
}
/* MANDATORY: Use :target-current to style the link whose target is in view. */
nav a:target-current {
color: var(--accent, #007bff);
border-bottom-color: var(--accent, #007bff);
}
<nav>
<a href="#intro">Introduction</a>
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
</nav>
<main>
<!-- MANDATORY: Each section must have a unique ID matching a link fragment in the <nav>. -->
<section id="intro">
<h2>Introduction</h2>
<!-- content -->
</section>
<section id="features">
<h2>Features</h2>
<!-- content -->
</section>
<section id="pricing">
<h2>Pricing</h2>
<!-- content -->
</section>
</main>
<script type="module">
if (CSS.supports('scroll-target-group: auto')) {
const syncAriaCurrent = () => {
const currentLink = document.querySelector('nav a:target-current');
document.querySelectorAll('nav a').forEach(link => {
link.setAttribute('aria-current', link === currentLink ? 'true' : 'false');
});
};
// Update the aria on initial page load. Ensure the DOM content is loaded, either by using `<script type="module">` or an event listener for `DOMContentLoaded`.
syncAriaCurrent();
// Update the aria after scroll ends
document.addEventListener('scrollend', syncAriaCurrent);
}
</script>
ベスト プラクティス
- 推奨: ナビゲーション リンク専用のコンテナで
scroll-target-group: autoを使用します。これにより、ブラウザはどのリンクをトラッキングすべきかを認識できます。 - 推奨: ターゲットに、リンクの
href属性と一致する一意のid属性があることを確認します。 - 推奨: セクションに十分な垂直方向のスペースを確保します。セクションが十分に大きく、一度に 1 つまたは 2 つしか表示されない場合、
scroll-target-groupは表示されているターゲットを最も明確に識別します。 - 必須: ユーザー補助のため、視覚的な状態を
aria-currentでミラーリングする必要があります。:target-currentは視覚的な側面を処理しますが、スクリーン リーダーには公開されません。 - AVOID: アクティブな状態を色だけで示す。フォントの太さ、下線、インジケーター ドットなどの他の視覚的な手がかりを含めるか、色のコントラストを十分に確保して、色覚異常のあるユーザーでも違いを認識できるようにします。
代替戦略
scroll-target-group をサポートしていないブラウザでは、IntersectionObserver API に基づくフォールバックを使用する必要があります。
<script>
// Feature detection for scroll-target-group
const supportsScrollTargetGroup = CSS.supports('scroll-target-group: auto');
if (!supportsScrollTargetGroup) {
const navLinks = document.querySelectorAll('nav a');
const sections = document.querySelectorAll('main section');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinks.forEach(link => {
const isActive = link.getAttribute('href') === `#${id}`;
// Use the escaped colon class for consistent styling
link.classList.toggle('\:target-current', isActive);
link.setAttribute('aria-current', isActive);
});
}
});
}, { rootMargin: '-50% 0px -50% 0px', threshold: 0 });
sections.forEach(section => observer.observe(section));
}
</script>
<style>
/* Fallback style if :target-current is not supported.
Use :is() to combine the native pseudo-class with a fallback class for older browsers. DO NOT use `nav a:target-current, nav a.\:target-current, as the unrecognized pseudoselector invalidates the entire rule in older browsers. */
nav a:is(:target-current, .\:target-current) {
color: var(--accent, #007bff);
border-bottom-color: var(--accent, #007bff);
font-weight: bold;
}
</style>