Scrollspy垂直侧边栏菜单:滚动时保持当前激活项可视的方案求解
垂直Scrollspy菜单解决方案
问题说明
开发垂直Scrollspy菜单时遇到以下问题:
- 页面滚动时,侧边栏导航无法自动滚动,章节较多时看不到当前激活的导航项
- 尝试Bootstrap Scrollspy组件未生效
核心修复点
1. 让导航跟随激活项滚动
修改导航的CSS样式,使其支持垂直滚动,并在激活项变化时自动滚动到可视区域:
- 将导航的
overflow-x: scroll改为overflow-y: auto,并设置height: 100vh使其占满视口高度 - 在激活导航项时,调用
scrollIntoView()方法让激活项滚动到导航的可视区域内
2. 优化Scrollspy逻辑
原代码存在循环覆盖、判断不准确的问题,优化后:
- 使用
Array.from(sections).find()找到当前处于视口的章节,避免循环重复操作 - 用
getBoundingClientRect()更准确判断章节是否在视口内 - 移除冗余的最后章节判断逻辑,统一视口判断规则
完整代码示例
HTML
<nav> <a href="#section-1" class="active">Section 1</a> <a href="#section-2">Section 2</a> <a href="#section-3">Section 3</a> <a href="#section-4">Section 4</a> <a href="#section-5">Section 5</a> <a href="#section-6">Section 6</a> <a href="#section-7">Section 7</a> <a href="#section-8">Section 8</a> <a href="#section-9">Section 9</a> <a href="#section-10">Section 10</a> </nav> <div class="section-container" tabindex="0"> <section id="section-1">Section 1 <p>(200px height)</p> </section> <section id="section-2">Section 2 <p>(800px height)</p> </section> <section id="section-3">Section 3 <p>(400px height)</p> </section> <section id="section-4">Section 4 <p>(1200px height)</p> </section> <section id="section-5">Section 5 <p>(600px height)</p> </section> <section id="section-6">Section 6 <p>(600px height)</p> </section> <section id="section-7">Section 7 <p>(600px height)</p> </section> <section id="section-8">Section 8 <p>(600px height)</p> </section> <section id="section-9">Section 9 <p>(600px height)</p> </section> <section id="section-10">Section 10 <p>(600px height)</p> </section> </div>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; position: relative; } html { scroll-behavior: smooth; overflow-y: scroll; height: 100vh; } nav { display: flex; position: fixed; flex-direction: column; top: 0; width: 20%; height: 100vh; /* 占满视口高度 */ overflow-y: auto; /* 垂直滚动 */ } nav a { padding: 10px 25px; color: black; font-size: 20px; text-transform: capitalize; background-color: lightgray; font-family: sans-serif; text-align: center; text-decoration: none; } nav a.active, nav a:hover { background-color: darkgray; } section { display: flex; align-items: center; justify-content: center; font-weight: 800; text-transform: uppercase; font-size: 7rem; font-family: sans-serif; border: 1px solid gray; flex-direction: column; } section p { font-size: 1rem; font-weight: 300; } section:nth-child(odd) { background-color: #eee; } .section-container { width: 80%; margin-left: auto; } #section-1 { height: 200px; } #section-2 { height: 800px; } #section-3 { height: 400px; } #section-4 { height: 1200px; } #section-5 { height: 600px; } #section-6 { height: 600px; } #section-7 { height: 600px; } #section-8 { height: 600px; } #section-9 { height: 600px; } #section-10 { height: 600px; }
JavaScript
const sections = document.querySelectorAll("section"); const navLinks = document.querySelectorAll("nav a"); const nav = document.querySelector("nav"); window.addEventListener('scroll', () => { // 找到当前处于视口的章节(顶部进入视口1/3位置时激活) const currentSection = Array.from(sections).find(section => { const rect = section.getBoundingClientRect(); // 当章节顶部进入视口下方1/3位置时触发激活 return rect.top <= window.innerHeight / 3 && rect.bottom >= window.innerHeight / 3; }); if (currentSection) { const id = currentSection.getAttribute('id'); // 移除所有激活类 navLinks.forEach(link => link.classList.remove('active')); // 给对应导航项添加激活类 const activeLink = document.querySelector(`nav a[href="#${id}"]`); activeLink.classList.add('active'); // 让激活项滚动到导航可视区域 activeLink.scrollIntoView({ block: 'center', behavior: 'smooth' }); } });
关键说明
- 导航设置为
height:100vh和overflow-y:auto,保证在章节过多时可以内部滚动 - 使用
getBoundingClientRect()判断章节位置,相比offsetTop更准确适配不同布局 - 激活项滚动时使用
scrollIntoView({block:'center'}),确保激活项始终出现在导航可视区域中间 - 优化后的Scrollspy逻辑只处理当前激活的章节,避免不必要的循环操作
内容的提问来源于stack exchange,提问作者Jolie
相关产品推荐
相关产品推荐

