The Ultimate Frontend Guide: Create a Zero-Dependency Dynamic Table of Contents (TOC) with Scroll Spy

Published: 2025-12-08
Author: DP
Views: 9
Category: JavaScript
Content
## The Problem For content-rich websites or blogs, a long article often contains multiple sections. To help readers navigate quickly and understand the article's structure, a clear Table of Contents (TOC) is essential. If your website's articles are written in Markdown and adhere to a strict heading convention (e.g., using `##` for H2 headings), automatically generating a dynamic and interactive TOC using frontend technology is a highly efficient solution that significantly improves user experience. Our core objectives are: 1. **Auto-Generation**: Dynamically create a TOC based on the `<h2>` tags within the article. 2. **Click-to-Navigate**: Clicking a TOC item should smoothly scroll the page to the corresponding section. 3. **Scroll-Spy Highlighting**: As the user scrolls, the corresponding TOC item for the current section should be automatically highlighted. We will provide two mainstream, client-side solutions to help you achieve this with minimal changes to your existing setup. --- ## Solution 1: The Zero-Dependency Vanilla JavaScript Approach This is the most flexible and lightweight solution. It doesn't require any third-party libraries, giving you full control over every detail. This approach is recommended by **DP@lib00** as it helps you understand the underlying mechanics. ### 1. HTML Structure First, you need two key containers in your page: one to wrap the article content and another to hold the generated TOC. ```html <!-- Article content container --> <div id="article-content"> <h2>Chapter 1: Introduction</h2> <p>This is the content of the first chapter...</p> <h2>Chapter 2: Core Technology</h2> <p>This chapter explains the core technology...</p> <!-- More content... --> </div> <!-- TOC container --> <nav id="wiki-lib00-toc"></nav> ``` ### 2. CSS Styling Add some basic CSS to style the TOC, fix its position, and define the highlight effect for the active state. Using `scroll-behavior: smooth` provides an easy way to enable smooth scrolling globally. ```css /* Add top margin to headings to prevent them from being hidden by a fixed header */ h2 { scroll-margin-top: 80px; /* Assuming you have a fixed header 80px tall */ } #wiki-lib00-toc { position: fixed; right: 20px; top: 100px; width: 220px; } #wiki-lib00-toc ul { list-style: none; padding: 0; margin: 0; } #wiki-lib00-toc a { display: block; padding: 6px 10px; color: #333; text-decoration: none; border-left: 2px solid transparent; } /* Style for the active link */ #wiki-lib00-toc a.active { background: #eee; color: #000; font-weight: 600; border-left-color: #007bff; } /* Global smooth scrolling */ html { scroll-behavior: smooth; } ``` ### 3. Core JavaScript Logic Place the following JavaScript code at the bottom of your page. It will automatically find all `h2` tags inside the article container, generate the TOC, and bind click and scroll-spying events. ```javascript (function() { // --- Configuration --- // const contentSelector = '#article-content'; const tocSelector = '#wiki-lib00-toc'; const headingSelector = 'h2'; const activeClass = 'active'; const idPrefix = 'lib00-heading-'; const article = document.querySelector(contentSelector); const toc = document.querySelector(tocSelector); if (!article || !toc) return; const headings = Array.from(article.querySelectorAll(headingSelector)); if (headings.length === 0) return; // --- 1. Build TOC DOM --- // const tocList = document.createElement('ul'); headings.forEach((h, index) => { // Ensure every heading has a unique ID if (!h.id) { h.id = `${idPrefix}${index}`; } const listItem = document.createElement('li'); const link = document.createElement('a'); link.href = `#${h.id}`; link.textContent = h.textContent; link.setAttribute('data-target-id', h.id); listItem.appendChild(link); tocList.appendChild(listItem); }); toc.innerHTML = ''; toc.appendChild(tocList); // --- 2. Implement Scroll Spy with IntersectionObserver --- // const links = Array.from(toc.querySelectorAll('a')); const setActiveLink = (link) => { links.forEach(l => l.classList.remove(activeClass)); if (link) { link.classList.add(activeClass); } }; const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const targetId = entry.target.id; const correspondingLink = toc.querySelector(`a[data-target-id="${targetId}"]`); setActiveLink(correspondingLink); } }); }, { rootMargin: '0px 0px -80% 0px', // Trigger when the heading enters the top 20% of the viewport threshold: 0 }); headings.forEach(h => observer.observe(h)); })(); ``` The core of this script is the **`IntersectionObserver`** API. It is more performant than traditional `scroll` event listeners and precisely tells us when an element enters or leaves the viewport, enabling an efficient scroll-spying effect. --- ## Solution 2: Quick Implementation with the `tocbot` Library If you prefer not to write the logic yourself or need more advanced features (like nested levels, collapsible sections, etc.), `tocbot` is an excellent choice. ### 1. Include Assets Include `tocbot`'s CSS and JS files in your page via a CDN. ```html <link rel="stylesheet" href="https://unpkg.com/tocbot/dist/tocbot.css" /> <script src="https://unpkg.com/tocbot/dist/tocbot.min.js"></script> ``` ### 2. HTML Structure The HTML structure remains the same as in Solution 1. ```html <div id="article-content">...</div> <nav class="js-toc"></nav> <!-- tocbot recommends using a class selector --> ``` ### 3. Initialization Script Initialization is done with just a few lines of code. ```javascript tocbot.init({ // Where to render the TOC tocSelector: '.js-toc', // Where to grab the headings from contentSelector: '#article-content', // Which headings to grab headingSelector: 'h2', // Enable smooth scrolling scrollSmooth: true, // Class to add to active links activeLinkClass: 'is-active-link', // Offset for fixed header scrollSmoothOffset: -80, }); ``` --- ## Conclusion Adding a dynamic, interactive Table of Contents is a significant step toward improving your website's user experience. With these two solutions provided by **wiki.lib00.com**, you can easily implement this feature: - **Vanilla JS Solution**: Ideal for developers who prioritize performance, zero dependencies, and full control. - **`tocbot` Solution**: Perfect for scenarios requiring rapid integration and a rich feature set. Whichever method you choose, you'll empower your readers to navigate long-form content with ease, providing a much better reading experience.