响应式布局中监听滚动位置,实现侧边导航aria-current切换
解决方案
一、无JS方案的可行性
不存在纯HTML/CSS的无JS方案能实现动态添加/移除aria-current="true"属性。原因是aria-current属于DOM属性,纯前端静态技术无法修改DOM属性——虽然可以用:target伪类给锚点对应的导航项添加视觉选中样式,但这只是视觉层面的模拟,无法满足语义化的ARIA属性要求,因此必须使用JavaScript实现。
二、基于IntersectionObserver的实现方案
按照你的需求,我们可以通过不可见触发元素+自定义相交区域的方式,精准监听章节是否到达导航栏下方50-75px的范围,同时处理锚点跳转后的状态同步。
1. 核心思路
- 给每个带ID的章节上方插入不可见的触发div,作为相交检测的标记点;
- 配置
IntersectionObserver的rootMargin,将相交区域限定在导航栏下方60px(取50-75的中间值)的水平线位置; - 监听触发元素的相交状态,动态给对应导航项添加/移除
aria-current; - 监听锚点跳转事件,主动同步导航项状态,确保跳转后即使章节不在触发区域,也能保持选中直到滚动离开。
2. 代码实现
HTML结构
<!-- 固定导航栏 --> <header class="navbar" style="height: 60px; position: fixed; top: 0; width: 100%; background: #fff;">导航栏</header> <!-- 侧边栏导航 --> <aside class="sidebar" style="position: fixed; left: 0; top: 60px; width: 200px;"> <nav> <ul style="list-style: none; padding: 0;"> <li><a href="#section1" class="nav-link">章节1</a></li> <li><a href="#section2" class="nav-link">章节2</a></li> <li><a href="#section3" class="nav-link">章节3</a></li> </ul> </nav> </aside> <!-- 内容区域 --> <main class="content" style="margin-left: 220px; margin-top: 60px;"> <!-- 不可见触发元素 --> <div class="section-trigger" data-section-id="section1"></div> <section id="section1" style="height: 800px; padding: 20px;"> <h2>章节1内容</h2> </section> <div class="section-trigger" data-section-id="section2"></div> <section id="section2" style="height: 800px; padding: 20px;"> <h2>章节2内容</h2> </section> <div class="section-trigger" data-section-id="section3"></div> <section id="section3" style="height: 800px; padding: 20px;"> <h2>章节3内容</h2> </section> </main> <!-- 页脚 --> <footer class="footer" style="height: 200px; background: #eee;">页脚</footer>
CSS样式
/* 隐藏触发元素,仅用于相交检测 */ .section-trigger { position: absolute; top: -60px; /* 与导航栏高度对齐,确保触发点在章节上方、导航栏下方 */ width: 100%; height: 1px; /* 最小高度保证能被Observer检测到 */ visibility: hidden; } .content { position: relative; /* 确保触发元素的绝对定位基于内容区域 */ }
JavaScript逻辑
// 获取导航栏高度,适配响应式变化 function getNavbarHeight() { return document.querySelector('.navbar').offsetHeight; } // 初始化/更新IntersectionObserver function initSectionObserver() { // 清除旧的观察者(避免重复监听) if (window.sectionObserver) { window.sectionObserver.disconnect(); } const navbarHeight = getNavbarHeight(); // 自定义相交区域:导航栏下方60px的水平线 // rootMargin计算:顶部偏移-(导航栏高度+60px),底部偏移-(视口高度-导航栏高度-60px) const rootMargin = `-${navbarHeight + 60}px 0 calc(-100vh + ${navbarHeight} + 60px) 0`; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { const sectionId = entry.target.dataset.sectionId; const navLink = document.querySelector(`.nav-link[href="#${sectionId}"]`); if (!navLink) return; if (entry.isIntersecting) { // 移除所有导航项的aria-current document.querySelectorAll('.nav-link[aria-current="true"]').forEach(link => { link.removeAttribute('aria-current'); }); // 给当前章节对应的导航项添加属性 navLink.setAttribute('aria-current', 'true'); } else { // 滚动离开章节区域时移除属性 navLink.removeAttribute('aria-current'); } }); }, { root: null, // 以视口为根元素 rootMargin: rootMargin, threshold: 0 // 只要触发元素进入相交区域就触发 }); // 监听所有触发元素 document.querySelectorAll('.section-trigger').forEach(trigger => { observer.observe(trigger); }); window.sectionObserver = observer; } // 处理锚点跳转后的导航状态同步 function syncNavWithHash() { const hash = window.location.hash.slice(1); if (!hash) return; document.querySelectorAll('.nav-link[aria-current="true"]').forEach(link => { link.removeAttribute('aria-current'); }); const targetLink = document.querySelector(`.nav-link[href="#${hash}"]`); if (targetLink) { targetLink.setAttribute('aria-current', 'true'); } } // 绑定事件监听 document.addEventListener('DOMContentLoaded', () => { initSectionObserver(); syncNavWithHash(); // 监听窗口大小变化,更新观察者(适配导航栏高度变化) window.addEventListener('resize', initSectionObserver); // 监听锚点变化 window.addEventListener('hashchange', syncNavWithHash); // 监听导航链接点击,确保跳转后状态同步 document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', () => { setTimeout(syncNavWithHash, 100); // 等待页面跳转完成后同步 }); }); });
3. 关键细节说明
- 响应式适配:通过
resize事件重新计算导航栏高度,更新Observer的rootMargin,确保在不同视口下相交区域始终精准; - 触发元素设计:使用不可见的小高度div作为触发点,避免章节标题高度变化影响检测精度;
- 锚点跳转处理:同时监听
hashchange和导航链接点击事件,主动同步导航状态,确保跳转后即使章节不在触发区域,也能保持选中直到用户滚动离开。
内容的提问来源于stack exchange,提问作者jimmymcheung
相关产品推荐
相关产品推荐

