滚动监听导航是一种常见的界面模式,可自动突出显示与当前视口中的页面部分对应的导航链接。这样,用户在滚动浏览长文档时,就能清楚地知道自己所在的位置。
此模式在以下两种场景中很常见:
- 单页网站:主网站导航链接到单个网页的不同部分,而不是单独的网址。
- 长篇内容:通过目录或导航边栏跟踪用户在文章、指南或文档中的阅读进度。
如何实现
传统上,滚动监听模式需要 JavaScript 和 IntersectionObserver API。现代 CSS 提供了一种原生方式,可以使用 scroll-target-group 属性和 :target-current 伪类来实现此目的。
- 确保 Navigation Links 使用 Fragment Identifiers:您的 Navigation 必须由锚链接 (
<a>) 组成,这些链接使用与内容部分id匹配的 Fragment Identifiers(例如<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>