Scrollspy 導覽是一種常見的 UI 模式,會自動醒目顯示與目前檢視區中網頁部分對應的導覽連結。這樣一來,使用者在捲動長篇文件時,就能清楚知道自己所在的位置。
這種模式常見於以下兩種情況:
- 單頁網站:主要網站導覽連結會連往單一網頁的不同部分,而非個別網址。
- 長篇內容:目錄或側欄導覽會追蹤使用者在文章、指南或說明文件中的閱讀進度。
導入方式
傳統上,Scrollspy 模式需要 JavaScript 和 IntersectionObserver API。現代 CSS 提供原生方式,可使用 scroll-target-group 屬性和 :target-current 虛擬類別達成這項效果。
- 確保導覽連結使用片段 ID:導覽必須由錨點連結 (
<a>) 組成,並使用與內容區段id相符的片段 ID (例如<a href="#section-1">) 指向同一頁面上的區段。 - 啟用捲動目標分組:將
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屬性。 - 建議做法:為各部分提供足夠的垂直空間:當各部分夠大,一次只能顯示一或兩個部分時,
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>