You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用纯JavaScript实现滚动时导航菜单高亮及子菜单展开折叠?

How to Implement Scroll-Active Sidebar Navigation with Expandable Submenus (Vanilla JS)

Hey there! Let’s walk through building that scroll-responsive sidebar navigation you’re after—where menu items highlight as you scroll, and submenus expand only when their parent is active (with everything collapsed otherwise). No third-party libraries required, just plain JavaScript. Here’s a step-by-step breakdown that’s tried and tested:

1. Set Up Your HTML Structure First

You’ll need a sidebar navigation that links to corresponding content sections on the page. Use data attributes to map menu items to their target sections, and nest submenus inside parent list items:

<!-- Sidebar Navigation -->
<nav class="sidebar">
  <ul class="nav-list">
    <li class="nav-item">
      <a href="#intro" class="nav-link" data-target="#intro">Introduction</a>
    </li>
    <li class="nav-item has-submenu">
      <a href="#features" class="nav-link" data-target="#features">Features</a>
      <ul class="submenu">
        <li class="submenu-item">
          <a href="#feature1" class="submenu-link" data-target="#feature1">Feature 1</a>
        </li>
        <li class="submenu-item">
          <a href="#feature2" class="submenu-link" data-target="#feature2">Feature 2</a>
        </li>
      </ul>
    </li>
    <li class="nav-item">
      <a href="#conclusion" class="nav-link" data-target="#conclusion">Conclusion</a>
    </li>
  </ul>
</nav>

<!-- Page Content Sections -->
<section id="intro" class="content-section">...</section>
<section id="features" class="content-section">...</section>
<section id="feature1" class="content-section">...</section>
<section id="feature2" class="content-section">...</section>
<section id="conclusion" class="content-section">...</section>

2. Add Basic CSS for Highlighting & Submenu Toggle

Style the active states and submenu visibility to make the behavior visible:

/* Default nav styles */
.nav-link, .submenu-link {
  display: block;
  padding: 8px 16px;
  text-decoration: none;
  color: #333;
  transition: color 0.2s ease;
}

/* Active state for links */
.nav-link.active, .submenu-link.active {
  color: #007bff;
  font-weight: bold;
}

/* Submenu default state (collapsed) */
.submenu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
  padding-left: 20px;
}

/* Submenu expanded state */
.submenu.show {
  max-height: 200px; /* Adjust based on your submenu size */
}

3. Core JavaScript Logic

Now let’s build the functionality in plain JS. We’ll break this into key functions:

3.1 Debounce Function (For Performance)

Scroll events fire constantly—use a debounce to limit how often our logic runs, avoiding jank:

function debounce(func, delay = 100) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

3.2 Detect Active Section

This function checks which content section is currently in the "active" viewport area (we’ll use the top 20% of the viewport as our trigger point for better UX, so the highlight feels natural):

function getActiveSection() {
  const sections = document.querySelectorAll('.content-section');
  let activeSectionId = '';
  
  const viewportHeight = window.innerHeight;
  const triggerPoint = viewportHeight * 0.2; // Trigger when section hits 20% from top

  sections.forEach(section => {
    const rect = section.getBoundingClientRect();
    // Check if section is above the trigger point and not fully scrolled past
    if (rect.top <= triggerPoint && rect.bottom > triggerPoint) {
      activeSectionId = `#${section.id}`;
    }
  });

  return activeSectionId;
}

3.3 Update Navigation & Submenus

This function updates the active state of menu items and toggles submenus:

function updateNavigation() {
  const activeSectionId = getActiveSection();
  if (!activeSectionId) return;

  // Remove active state from all links
  document.querySelectorAll('.nav-link, .submenu-link').forEach(link => {
    link.classList.remove('active');
  });

  // Collapse all submenus
  document.querySelectorAll('.submenu').forEach(submenu => {
    submenu.classList.remove('show');
  });

  // Find the active link
  const activeLink = document.querySelector(`[data-target="${activeSectionId}"]`);
  if (!activeLink) return;

  // Add active state to the link
  activeLink.classList.add('active');

  // If the active link is in a submenu, expand its parent submenu and highlight parent
  const parentNavItem = activeLink.closest('.has-submenu');
  if (parentNavItem) {
    const parentLink = parentNavItem.querySelector('.nav-link');
    parentLink.classList.add('active');
    parentNavItem.querySelector('.submenu').classList.add('show');
  } 
  // If the active link is a parent item, expand its submenu
  else if (activeLink.closest('.nav-item').classList.contains('has-submenu')) {
    activeLink.closest('.nav-item').querySelector('.submenu').classList.add('show');
  }
}

3.4 Initialize & Attach Scroll Listener

Run the update function on page load and attach the debounced scroll listener:

// Initialize on page load to set initial active state
window.addEventListener('DOMContentLoaded', updateNavigation);

// Attach debounced scroll listener to update on scroll
window.addEventListener('scroll', debounce(updateNavigation));

// Optional: Update on viewport resize (also debounced)
window.addEventListener('resize', debounce(updateNavigation));

4. Extra Tips for Polish

  • Smooth Scrolling: Add scroll-behavior: smooth; to your CSS <html> tag for nicer anchor link scrolling.
  • Bottom-of-Page Edge Case: Adjust the getActiveSection logic to ensure the last section gets highlighted when scrolled to the very bottom of the page.
  • Accessibility: Add ARIA attributes like aria-expanded to parent menu items to make the submenu behavior accessible to screen readers.

内容的提问来源于stack exchange,提问作者Himanshu sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:19:10