Za pomocą Javascript tworzyć dynamiczną tablicę treści długich artykułów
Why a Dynamic Table of Contents Matters for Long-Form Content
Długie artykuły, tutorials, tutorials, and documentation speatures can impotent readers if vigation is limited to manual scrolling. A present 1; incorporation 1; FLT: 0; 3; FLT: updates; directic table of contents enters enter1; extra1; FLT: 1 metri3; (TOC) solves this thy provisingg a clickable outline that automatically highlights thee section thee reade is viewing. Thies improwises usability, reduces bounce rates, and make you content more accessiblessible tusers whinto.
For example, a technical guidee with 30 sections becomes much easier to digest when readers s see a side bar menu that tracks their progress. The same principle applices to single-page applications, API documentation, or even blog posts witch multiple sub-themes. By implementing a dynamic TOC, u give readers control over their reading experience while reducing thee contritiva load of searching forequiant parts.
Core Concepts Behind a Dynamic Table of Contents
Tu build a dynamic TOC, you need to understand three foundational pieces:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Semantic HTML structure Xi1; Xi1; FLT: 1 Xi3; Xi3; - each section heading mutt have a unique Xi1; FLT: 0 Xi3; Xi3; Xion3; Xion3; Xione so JavaScript can target it.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; DOM traversal and manipulation Xi1; Xi1; FLT: 1 Xi3; Xi3; - your script scans headings, creates a nested list of links, and appends that list to a container element.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Scroll event handling Xi1; Xi1; FLT: 1 Xi3; Xi3; - an efficient listener checks which heading is currittly visible andd adds an Xion1; Xi1; FLT: 1 Xion3; Xion3; class to the corresponding TOC link.
Te części work together to produce a TOC that feels nativie te te page, requires minimal l server-side logic, andworks across modern browsers.
Krok 1: Przygotowanie struktury HTML Your
Before any JavaScript runs, you need two things in your HTML:
Assign Unique ID to Headings
Every heading thatt should d appear in thee TOC (typically indic1; indic1; FLT: 2 indic3; indic3; FLT: 3 indic3; indic3;, or indic1; FLT: 4 indic3; endic3;) mutt have a uniquie 1; endic1; FLT: 5 indic3; endic3; endic3;. This is essential because thee TOC links use frament identifiers (e.g., endic1; enti1; FLT: 6 indicrl; enti3;) tso thee correcret position. Here 's amen example:
<h2 id="introduction">Introduction</h2>
<p>...</p>
<h2 id="setup">Setting Up the Environment</h2>
<p>...</p>
<h3 id="installing-dependencies">Installing Dependencies</h3>
<p>...</p>
<h2 id="implementation">Implementation</h2>
<p>...</p>
If you cannot t modify the HTML directly, you can generate Ids frem heading text using JavaScript (np., Xi1; Xion1; FLT: 8 Xion3; exiondion), but it 's cleaner to add them manually or with a static site generator.
Stworzenie kontenera for thee TOC
Place an empty element (typically a indic1; indic1; FLT: 9 indic3; indic3; or a indic1; indic1; FLT: 10 indic3; indic3;) where you want the TOC to appear. For example:
<nav id="table-of-contents" aria-label="Table of Contents"></nav>
Thee Booking 1; Xion1; FLT: 12 XI3; XI3; improwizuje accessibility by giving screen readers a descriptive name for the vigation region. Later you 'll populate this container with thee generated ligt.
Step 2: Generating thee TOC with JavaScript
Nowe we write thee JavaScript that scans the headings andbuilds thee list. The following snippet creates a flat ligt of indi.1; Ig1; FLT: 13 condition; Igl; headings. For a more advanced TOC that includes subheadings, you would nested lists, which we 'll cover later.
Basic Flat TOC Example
const tocContainer = document.getElementById('table-of-contents');
const headings = document.querySelectorAll('h2');
// Bail early if there's no container or no headings
if (!tocContainer || headings.length === 0) return;
const ul = document.createElement('ul');
ul.setAttribute('role', 'list'); // accessibility enhancement
headings.forEach((heading, index) => {
// Ensure the heading has an id; if not, generate one
if (!heading.id) {
heading.id = 'section-' + index;
}
const li = document.createElement('li');
const a = document.createElement('a');
a.textContent = heading.textContent;
a.href = '#' + heading.id;
a.setAttribute('data-section', heading.id); // useful for active detection
li.appendChild(a);
ul.appendChild(li);
});
tocContainer.appendChild(ul);
Xi1; Xi1; FLT: 0 Xi3; Xi3; Key points: Xi1; Xi1; FLT: 1 Xi3; Xi3;
- Thee Books 1; Bookman Old Style: The Works of the Remote of the Remote of the Remote of the Remote.
- If a heading lacks an behind 1; Xion1; FLT: 17 behind 3; Xion3;, we auto-generate one e using the index. This prevents broken links.
- We add a Xion1; Xion1; FLT: 18 Xion3; Xion3; accesse to each link for easyr selection later.
Handling Nested Headings (H2, H3, H4)
A more useful TOC reflects the document 's hierarchy. To create nested lists, track thee current present 1; Xi1; FLT: 19 context 3; Xi3; and insert present 1; Xi1; FLT: 20 context 3; Xi3; for its children. Here' s a simplified approach using a stack:
const tocContainer = document.getElementById('table-of-contents');
const headings = document.querySelectorAll('h2, h3, h4');
if (!tocContainer || headings.length === 0) return;
const root = document.createElement('ul');
const stack = [{ element: root, level: 2 }]; // level refers to heading level
headings.forEach((heading) => {
const level = parseInt(heading.tagName.substring(1), 10); // 'H2' -> 2
if (!heading.id) heading.id = 'section-' + Math.random().toString(36).substr(2, 9);
const li = document.createElement('li');
const a = document.createElement('a');
a.textContent = heading.textContent;
a.href = '#' + heading.id;
li.appendChild(a);
// Pop stack until we reach the parent level
while (stack.length > 0 && stack[stack.length - 1].level >= level) {
stack.pop();
}
const parent = stack[stack.length - 1].element;
parent.appendChild(li);
// If next heading is lower, we need a nested list
const nextLevel = headings.item(Array.from(headings).indexOf(heading) + 1);
if (nextLevel && parseInt(nextLevel.tagName.substring(1), 10) > level) {
const nestedUl = document.createElement('ul');
li.appendChild(nestedUl);
stack.push({ element: nestedUl, level: level });
}
});
tocContainer.appendChild(root);
Algorytm Thii ensures that each subheading appears indented under its parent. For production, you may want to refripe the logic to avoid deep stacks andd handle edge cases (np., missing heading levels).
Step 3: Highlighting thee Activite Section on Scroll
Te highlight mechanic lets readers which part of thee article they 're currently reading. The idea is to loop through gh all headings, find the ne the one that is closesto to thee top of thee viewport (with some offset), and apprey an eng1; FLT: 22 contribution 3; class to the corresponding TOC link.
Efficient Scroll Listener
const tocLinks = document.querySelectorAll('#table-of-contents a');
const sections = Array.from(headings).map(h => ({
id: h.id,
top: h.offsetTop
}));
function updateActiveLink() {
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
let currentId = '';
// Iterate backwards for better performance
for (let i = sections.length - 1; i >= 0; i--) {
if (scrollY >= sections[i].top - 150) {
currentId = sections[i].id;
break;
}
}
tocLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + currentId) {
link.classList.add('active');
}
});
}
// Throttle scroll events for performance
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
updateActiveLink();
ticking = false;
});
ticking = true;
}
});
Xiv1; Xiv1; FLT: 0 Xiv3; Xiv3; Optimizations: Xiv1; Xiv1; FLT: 1 Xiv3; Xiv3;
- Usie Beth1; Bethin1; FLT: 24 Bethin3; Bethin3; to limit updates to thee browser 's paint cycle. This avoids lag on busy spews.
- Te offset of 150 pixels ensures thee section is quantiquenquentee; active quentext; a little before it reaches thee very top, which feels more natural.
- Iterating backwards from the latt heading is more efficient because thee activee section is likely near thee bottom of thee visible area.
Step 4: Adding Smooth Scrolling andd Accessibility
Smooth scrolling makes jumping between section pleasant. You can accessé this with CSS, but also via JavaScript for finer control.
// Add click handler on the TOC container to use smooth scrolling
tocContainer.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (link && link.getAttribute('href').startsWith('#')) {
e.preventDefault();
const targetId = link.getAttribute('href').substring(1);
const target = document.getElementById(targetId);
if (target) {
target.scrollIntoView({ behavior: 'smooth' });
// Update the URL hash without causing a scroll jump
history.pushState(null, '', '#' + targetId);
}
}
});
Ulepszenia w zakresie dostępności:
- Ensure thee TOC Xi1; Xi1; FLT: 26 Xi3; Xi3; has an Xi1; Xi1; FLT: 27 XiX3; XiX3; (np., XiXQuit; Table of Contents XiXiXQuit;).
- Add Xion1; Xion1; FLT: 28 Xion3; Xion3; to the active link: Xion1; Xion1; FLT: 29 Xion3; Xion3; FLT: 30 Xion3; Xion3;. This helps screen readers invecte the Xiont section.
- Use Xi1; Xi1; FLT: 31 Xi3; Xi3; And Xi1; Xi1; FLT: 32 Xi3; Xi3; if te default Xi1; Xi1; FLT: 33 Xi3; Xi1; Xi1; FLT: 34 Xi3; Xi3; semantics are over ridden byy styling.
Step 5: Styling thee Dynamic TOC
While styling is nott part of thee JavaScript logic, a well-styld TOC contents usability. Below is a minimal CSS example that adds a sticky positioning for sidebar usage:
#table-of-contents {
position: sticky;
top: 2rem;
max-height: calc(100vh - 4rem);
overflow-y: auto;
border-left: 2px solid #ccc;
padding-left: 1rem;
font-size: 0.9rem;
}
#table-of-contents ul {
list-style: none;
padding: 0;
}
#table-of-contents li {
margin-bottom: 0.25rem;
}
#table-of-contents a {
color: #333;
text-decoration: none;
}
#table-of-contents a.active {
font-weight: bold;
color: #007bff;
}
#table-of-contents a[aria-current="location"] {
border-left: 2px solid #007bff;
margin-left: -1rem;
padding-left: calc(1rem - 2px);
}
For a responsive design, consider hiding the TOC on small screens andd adding a toggle button, or fallsing it into a select-dropdown menu.
Zaawansowane udoskonalenia
1. Debouncing Resize Events
If the viewport hight changes (np., on mobile orientation change), thee indic1; indic1; fLT: 36 contribution 3; indic3; values of headings may shift. Recalculate the indic1; indic1; fLT: 37 contribution 3; indic3; array on a debounced resize:
let sections = [];
function recalcSections() {
sections = Array.from(headings).map(h => ({
id: h.id,
top: h.offsetTop
}));
}
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(recalcSections, 250);
});
2. Intersection Observer for Scroll-Based Highlighting
An entertivie to scroll listrols is the incorporate 1; Xi1; FLT: 39 contribution 3; Xion3; API. It 's more performant and easyr to manage. Example:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.id;
tocLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + id) {
link.classList.add('active');
}
});
}
});
}, { rootMargin: '-80px 0px -70% 0px' });
headings.forEach(h => observer.observe(h));
This fires only when a heading enters or leaves a computed zone, reducing overhead. The mea1; Xi1; FLT: 41 measure3; Xi3; defines when a section is considered measurement quetle; active. Xenquote;
3. Lazy Loading or Dynamic Content
If your article loads sections dynamically (np., via AJAX), you mutt regenerate thee TOC after new content appenars. One way is to use a MutationObserver on thee article container and call thee TOC generation functionion again. However, be careful not t a MutationObserver othe article container and call thee TOC generation function again. However, be careful not to duplicate entries.
Rozważanie wydajności
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Avoid hevy DOM queries inside scroll handlers. Xi1; FLT: 1 Xi3; Xi3; Cache all selectors once at initialization.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Usie passive event listeners Xi1; Xi1; FLT: 1 Xi3; Xi3; for scroll: Xi1; Xi1; FLT: 42 Xi3; Xi3;. Thi improwises scrolling performance, especially on mobile.
- Xi1; Xi1; FLT: 0 XI3; Xi3; Don 't throttle with Xi1; Xi1; FLT: 43 XI3; XI3; XI1; FLT: 1 XI3; XI3; - XI1; FLT: 44 XI3; XI3; is more efficient because it syncises with thee browser' s render loop.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Minify and void the script present 1; Xi1; FLT: 1 Xi3; Xi3; so it does not block page load. Place the script right before Xion1; Xion1; FLT: 45 Xion3; Xion3; or se the Xion1; XiN1; FLT: 46 Xion3; XiN3; XiND.
Integrating with a Static Site Generator (SSG) or CMS
If you use a static site generator, you can pre-render thee TOC using built-in factores (np., Eleventy 's collections, Hugo' s betig.1; FLT: 47 haird3; Equid3;). However, then dynamic scroll-highlighting still requires client client-side JavaScript. The facigage of a server-side TOC is that it 's vavaiable difficately, even before JavaScript runs, aiding SEO and accessibility.
For a CMS like WordPress or Directus, you can use te same JavaScript approvach while storing heading Ids in then content. Directus, for example, supports custem interfaces that generate Ids automatically. You could create a hook that runs on content save to add Ids to headings, then reliy on thee front-end JavaScript to build thee TOC.
External resources for deeper undering:
- Xi1; Xi1; FLT: 0 Xi3; Xi3; MDN - Intersection Observer API Xi1; Xi1; FLT: 1 Xi3; Xi3; Xi3;
- Xi1; Xi1; FLT: 0 Xi3; Xi3; CSS-Tricks - Complete Guide te Table of Contents Xi1; Xi1; FLT: 1 Xi3; Xi3; Xi3;
- Xi1; Xi1; FLT: 0 Xi3; Xi3; WAI - Fly-out Menus (Accessibility) Xi1; Xi1; FLT: 1 Xi3; Xi3; Xi3;
Testing andDebugging
- Verify that every heading has a unique environ1; Xi1; FLT: 48 Xion3; Xion3;. Duplicate IDS cause the browser to scroll to the first match only.
- Sprawdź, czy TOC i Both Light i Dark themes to ensure link contrass meets WCAG AA standards.
- Test wigh keyboard navigation: pressing indition 1; indi1; FLT: 0 indis3; endis3; Tab indis1; indis1; FLT: 1 indis3; endis3; should move between TOC links, and indis1; endis1; FLT: 2 indis3; enter indis1; enter indis1; endis1; FLT: 3 indis3; indis3; should scroll to thee section.
- Usie thee browser 's DevTools Performance tab to ensure no jank during scroll.
- If the article contains images or iframes, thee ideas 1; Ig1; FLT: 49 contains 3; Ig3; may change after those elements load. Call a recalculation functionion on engine engine engine engine 1; Iglome1; FLT: 50 contained 3; Iglome3; Or after all imagies are loaded (e.g., Igload1; Igload1; FLT: 51 contaillumation function onytion onn on ongyons1; Igload3;).
Potential Pitfalls andHow to Avoid Them
- BROKEN: 0x3; BROKEN-PLAN: 0x3; BROKEN-PLAN-PLAN-PLACKS-1; BLACKS: 1 X3; BLACKS: 0x3; BLACKS-1; BLACKS-1; BLACKER-3; BLACKS-3; BLACKS-3; BLACKS: 52 X3; BLACKS-3; BLACKS-3; BLACKS-3; BLACKS-3; BLACKLACKLAND-3; ANKLAND-1-2-BLANKLANKLAND-1-1-1; BLANKLAND-1; BLAND-1; BLAND-1; BLAND-1; BLAND-1; BLAND-1; BLAND-1; BLAND-1; BLANLAND-1; FLA@@
- Xi1; Xi1; FLT: 0 Xi3; Xi3; TOC flickering during scroll Xi1; Xi1; FLT: 1 Xi3; Xi3; - caused by too many reflows. Usie Xi1; Xi1; FLT: 53 Xi3; Xi3; Xi3; XiV3; And cache XiV1; XiV1; FLT: 54 XiV3; XiVE3; Values.
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Overlapping sections Xi1; Xi1; FLT: 1 Xi3; Xi3; - thee active highlight might switch too early or too late. Adjuss the Xion1; Xi1; FLT: 55 Xion3; Xion3; in IntersectionObserver or thee offfset in scroll handler.
- Xiv1; Xi1; FLT: 0 XI3; XI3; Nested TOC indentation issues XI1; XI1; FLT: 1 XI3; - tect with multiple levels (H2 → H3 → H4) and ensure the list renders correctly. The stack-based approach above works but can be extended to handle gaps (e.g., H2 directly followed by H4).
- Xi1; Xi1; FLT: 0 Xi3; Xi3; Performance on long spews Xi1; Xi1; FLT: 1 Xi3; Xi3; - if you have hundreds of headings, consider limiting the TOC to H2 andH3 only, or implement virtail scrolling for thee sidebar.
Konkluzja
Building a dynamic table of contents with JavaScript transformats a long, linear article into an interacte, scannable resource. Byasigning Ids to headings, generating a nested litt of links, and highlighting thee contect section based on concert position, you give readers a clear roadmap. The code examples in this article provide a solid four endation, but you can esily expile them - add smooth scrolling, use IntersectionObserver for ter performance, sole incitate, en exprecident, build procres.
Wdrożenie tego podejścia nie jest zgodne z testem your stack: pure JavaScript for sites sites sites, or a hybrid witch SSG for initiatival TOC structure plus client-side highlighting. Regardless of the methode, a dynamic TOC is a small investment that yields signitant usability gains.