Sidebar-System – So funktioniert's
Eine verständliche Erklärung, wie die beiden Sidebars arbeiten, wie sie generiert werden und wie ScrollSpy den aktiven Abschnitt hervorhebt.
Das Sidebar-System im Überblick
DevPanicZone hat ein ausgeklügeltes Sidebar-System mit zwei unabhängigen Sidebars, die unterschiedlich funktionieren:
- Inhalt: Inhaltsverzeichnis der aktuellen Seite
- Generierung: Server-seitig via
generate-navigation.js - Basis:
h2&h3Überschriften - Features: ScrollSpy, Smooth Scroll
- Inhalt: Alle Tutorials der Kategorie
- Generierung: Server-seitig via
generate-navigation.js - Basis:
TUTORIAL_ORDERKonfiguration - Features: Active-Link Highlighting
- Server-seitig: HTML wird beim
npm run builderstellt und in die Datei geschrieben - Browser-seitig: JavaScript würde im Browser HTML erstellen (langsamer, fehleranfällig)
Das bedeutet: Wenn du die Seite öffnest, ist das HTML schon da! JavaScript ist nur für Interaktionen (Öffnen/Schließen, ScrollSpy) zuständig.
Architektur & Zusammenspiel
Mehrere Dateien arbeiten zusammen, um das Sidebar-System zu realisieren:
Die beteiligten Dateien
scripts/
└── generate-navigation.js → Generiert Sidebar-HTML
assets/
├── css/
│ └── sidebar.css → Styling beider Sidebars
└── js/
├── sidebar.js → Öffnen/Schließen, Event-Handling
├── scroll-spy.js → Active-Link beim Scrollen
└── main.js → Header, Footer, Theme Toggle
Ablauf: Von der Generierung bis zur Anzeige
- Build-Zeit:
generate-navigation.jserstellt HTML für beide Sidebars - HTML-Datei: Sidebar-HTML ist fest im HTML eingebettet
- Seite lädt: Browser rendert die Sidebars (noch nicht sichtbar)
- JavaScript lädt:
sidebar.jsundscroll-spy.jswerden aktiv - User-Interaktion: Klick auf Button → Sidebar öffnet sich
- Scrollen: ScrollSpy hebt aktiven Link hervor
Linke Sidebar (TOC) – Inhaltsverzeichnis
Die linke Sidebar zeigt ein Inhaltsverzeichnis der aktuellen Seite. Sie wird aus den Überschriften generiert.
1. Generierung (generate-navigation.js)
Die Funktion addIdsToHeadingsAndGenerateSidebar() macht folgendes:
function addIdsToHeadingsAndGenerateSidebar(html) {
const dom = new JSDOM(html);
const doc = dom.window.document;
// 1. Finde alle h2 und h3 Überschriften
const headings = doc.querySelectorAll('h2, h3');
const anchors = [];
const usedIds = new Set();
headings.forEach(heading => {
// Überspringe Überschriften mit class="no-toc"
if (heading.classList.contains('no-toc')) return;
let id = heading.id;
// 2. Generiere ID aus dem Text
if (!id) {
id = heading.textContent.trim().toLowerCase()
.replace(/[äöüÄÖÜß]/g, match => {
const map = { 'ä': 'ae', 'ö': 'oe', 'ü': 'ue' };
return map[match];
})
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
// 3. Vermeide Duplikate
let finalId = id;
let counter = 1;
while (usedIds.has(finalId)) {
finalId = `${id}-${counter}`;
counter++;
}
// 4. Füge ID zum Heading hinzu!
heading.id = finalId;
id = finalId;
}
usedIds.add(id);
// 5. Erstelle Sidebar-Link
const level = heading.tagName === 'H2' ? 'toc-level-1' : 'toc-level-2';
anchors.push(
`<li class="${level}"><a href="#${id}">${heading.textContent.trim()}</a></li>`
);
});
return {
html: dom.serialize(), // HTML mit IDs!
anchors: anchors.join('\n')
};
}
Schritt für Schritt
- Finde alle
<h2>und<h3>Überschriften - Überspringe Überschriften mit
class="no-toc" - Generiere eine ID aus dem Überschriften-Text (z.B. "Was ist CSS?" → "was-ist-css")
- Füge die ID direkt in die Überschrift ein:
<h2 id="was-ist-css"> - Erstelle einen Link:
<a href="#was-ist-css">Was ist CSS?</a> - Gib sowohl das modifizierte HTML als auch die Links zurück
2. HTML-Struktur
Das generierte HTML wird zwischen den Markern eingefügt:
<aside class="tutorial-sidebar" id="tutorialSidebar">
<div class="sidebar-header">
<h3 class="sidebar-title no-toc">Auf dieser Seite</h3>
<button class="sidebar-close" id="sidebarClose">×</button>
</div>
<nav class="sidebar-toc">
<ul class="toc-list">
<!-- Sidebar-Anchor-Start -->
<li class="toc-level-1"><a href="#was-ist-css">Was ist CSS?</a></li>
<li class="toc-level-1"><a href="#selektoren">Selektoren</a></li>
<li class="toc-level-2"><a href="#element-selektor">Element-Selektor</a></li>
<li class="toc-level-2"><a href="#class-selektor">Class-Selektor</a></li>
<!-- Sidebar-Anchor-End -->
</ul>
</nav>
</aside>
.toc-level-1→ h2-Überschriften (Hauptabschnitte).toc-level-2→ h3-Überschriften (Unterabschnitte, eingerückt)
3. Interaktivität (sidebar.js)
Das sidebar.js Script steuert das Öffnen und Schließen:
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('tutorialSidebar');
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebarClose = document.getElementById('sidebarClose');
const overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
document.body.appendChild(overlay);
// Öffne Sidebar
function openSidebar() {
sidebar.classList.add('is-open');
overlay.classList.add('is-visible');
document.body.style.overflow = 'hidden'; // Verhindert Scrollen
}
// Schließe Sidebar
function closeSidebar() {
sidebar.classList.remove('is-open');
overlay.classList.remove('is-visible');
document.body.style.overflow = '';
}
// Event Listeners
sidebarToggle.addEventListener('click', openSidebar);
sidebarClose.addEventListener('click', closeSidebar);
overlay.addEventListener('click', closeSidebar);
// ESC-Taste
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeSidebar();
});
// Schließe bei Klick auf TOC-Link
const tocLinks = document.querySelectorAll('.toc-link');
tocLinks.forEach(link => {
link.addEventListener('click', closeSidebar);
});
});
Was passiert beim Öffnen?
- Klasse
.is-openwird zur Sidebar hinzugefügt - CSS transform bewegt die Sidebar ins Sichtfeld
- Overlay wird sichtbar (dunkler Hintergrund)
- Body-Scrolling wird deaktiviert
4. Smooth Scroll mit Offset
Wenn du auf einen TOC-Link klickst, scrollt die Seite sanft zur Überschrift – mit einem Offset:
tocLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault(); // Verhindere Standard-Sprung
const targetId = this.getAttribute('href'); // z.B. "#was-ist-css"
const targetSection = document.querySelector(targetId);
if (targetSection) {
const headerOffset = 100; // 100px Abstand von oben
const elementPosition = targetSection.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
Warum der Offset?
- Der Header ist sticky und würde die Überschrift überdecken
- 100px Offset stellt sicher, dass die Überschrift sichtbar ist
ScrollSpy – Aktiven Link hervorheben
ScrollSpy ist ein System, das beim Scrollen den aktuell sichtbaren Abschnitt in der Sidebar hervorhebt.
Wie funktioniert ScrollSpy?
Das scroll-spy.js Script trackt deine Scroll-Position und vergleicht
sie mit den
Positionen der Überschriften:
class ScrollSpy {
constructor() {
this.tocLinks = document.querySelectorAll('.toc-list a');
this.headings = [];
this.currentActive = null;
this.ticking = false;
this.init();
}
init() {
if (this.tocLinks.length === 0) return;
// 1. Sammle alle Überschriften mit IDs
this.tocLinks.forEach(link => {
const id = link.getAttribute('href').substring(1); // Entferne #
const heading = document.getElementById(id);
if (heading) {
this.headings.push({
id: id,
element: heading,
link: link
});
}
});
// 2. Event Listeners
window.addEventListener('scroll', () => this.onScroll(), { passive: true });
// 3. Initial Check
this.updateActiveLink();
}
onScroll() {
// Throttling für Performance
if (!this.ticking) {
window.requestAnimationFrame(() => {
this.updateActiveLink();
this.ticking = false;
});
this.ticking = true;
}
}
updateActiveLink() {
const scrollPosition = window.scrollY + 100; // 100px Offset
let activeHeading = null;
// Finde die Überschrift, die gerade sichtbar ist
for (let i = 0; i < this.headings.length; i++) {
const heading = this.headings[i];
const headingTop = heading.element.offsetTop;
if (scrollPosition >= headingTop) {
activeHeading = heading;
} else {
break;
}
}
// Wenn ganz unten, nimm die letzte Überschrift
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 10) {
activeHeading = this.headings[this.headings.length - 1];
}
// Update nur wenn sich was geändert hat
if (activeHeading && activeHeading !== this.currentActive) {
this.setActiveLink(activeHeading);
}
}
setActiveLink(activeHeading) {
// Entferne .active von allen Links
this.tocLinks.forEach(link => link.classList.remove('active'));
// Setze .active auf den aktiven Link
if (activeHeading && activeHeading.link) {
activeHeading.link.classList.add('active');
this.currentActive = activeHeading;
}
}
}
// Initialisiere ScrollSpy
new ScrollSpy();
Schritt für Schritt erklärt
1. Initialisierung
Beim Laden der Seite:
- Finde alle Links in der TOC (
.toc-list a) - Für jeden Link: Finde die zugehörige Überschrift via ID
- Speichere Link + Überschrift zusammen in einem Array
// Link: <a href="#was-ist-css">Was ist CSS?</a>
// Heading: <h2 id="was-ist-css">Was ist CSS?</h2>
// Gespeichert als:
{
id: 'was-ist-css',
element: <h2> Element,
link: <a> Element
}
2. Scroll-Tracking
Beim Scrollen:
- Hole aktuelle Scroll-Position + 100px Offset
- Durchlaufe alle Überschriften von oben nach unten
- Finde die Überschrift, die gerade sichtbar ist (scrollPosition >= headingTop)
- Das ist der aktive Abschnitt!
Der Header ist sticky und nimmt Platz ein. Mit 100px Offset wird eine Überschrift als "aktiv" markiert, wenn sie ca. unter dem Header ist – nicht erst wenn sie ganz oben am Viewport klebt.
3. Performance-Optimierung (Throttling)
Das Scroll-Event wird SEHR oft gefeuert (bei jedem Pixel!). Um die Performance zu schonen:
onScroll() {
if (!this.ticking) {
window.requestAnimationFrame(() => {
this.updateActiveLink();
this.ticking = false;
});
this.ticking = true;
}
}
Was passiert hier?
requestAnimationFramewartet bis zum nächsten Browser-Repaint- Dadurch läuft die Funktion maximal 60x pro Sekunde (statt 1000x!)
tickingFlag verhindert mehrfache Aufrufe
4. Active-Klasse setzen
Wenn der aktive Abschnitt sich ändert:
- Entferne
.activevon ALLEN TOC-Links - Füge
.activezum aktiven Link hinzu - CSS hebt den Link hervor (z.B. blaue Farbe, fetter Text)
.toc-list a {
color: var(--text-secondary);
}
.toc-list a.active {
color: var(--accent-blue);
font-weight: 600;
border-left: 3px solid var(--accent-blue);
}
Edge Cases
ScrollSpy behandelt auch spezielle Fälle:
Ganz oben auf der Seite
Wenn keine Überschrift im Viewport ist → Erste Überschrift wird aktiv
Ganz unten auf der Seite
Wenn du am Ende der Seite bist → Letzte Überschrift wird aktiv (auch wenn sie nicht mehr sichtbar ist)
// Wenn ganz unten
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 10) {
activeHeading = this.headings[this.headings.length - 1];
}
Rechte Sidebar (Tutorial-Nav)
Die rechte Sidebar zeigt alle Tutorials der aktuellen Kategorie. Sie wird ebenfalls server-seitig generiert!
1. Generierung (generate-navigation.js)
Die Funktion generateSidebarNavList() erstellt die Tutorial-Liste:
function generateSidebarNavList(filePath) {
// 1. Finde die Unterkategorie
const subcategory = getSubcategoryFromPath(filePath);
// z.B. "tutorials/css/css-basics"
// 2. Hole die Tutorial-Liste aus TUTORIAL_ORDER
const tutorials = getOrderedTutorials(subcategory);
// 3. Finde die aktuelle URL
const currentUrl = getRelativeUrl(filePath);
// 4. Erstelle HTML für jedes Tutorial
return tutorials.map(tutorial => {
const isActive = tutorial.url === currentUrl;
const activeClass = isActive ? ' class="active"' : '';
return `<li><a href="${tutorial.url}"${activeClass}>${tutorial.title}</a></li>`;
}).join('\n');
}
Schritt für Schritt:
- Ermittle die Unterkategorie aus dem Dateipfad
- Hole die Tutorial-Liste aus
TUTORIAL_ORDER - Für jedes Tutorial: Erstelle einen Link
- Das aktuelle Tutorial bekommt
class="active" - Füge alle Links zusammen
2. HTML-Struktur
Das generierte HTML wird automatisch in die rechte Sidebar eingefügt:
<aside class="tutorial-related">
<div class="sidebar-header">
<h3 class="sidebar-title no-toc">Tutorials</h3>
<button class="sidebar-close" id="tutorialNavClose">×</button>
</div>
<div class="sidebar-nav">
<ul class="sidebar-nav-list">
<li><a href="/tutorials/css/css-basics/css-einbinden.html">CSS einbinden</a></li>
<li><a href="/tutorials/css/css-basics/css-selektoren.html" class="active">CSS Selektoren</a></li>
<li><a href="/tutorials/css/css-basics/css-box-model.html">CSS Box Model</a></li>
</ul>
</div>
</aside>
3. Interaktivität (sidebar.js)
Das Öffnen/Schließen funktioniert genau wie bei der linken Sidebar:
const tutorialRelated = document.querySelector('.tutorial-related');
const tutorialNavToggle = document.getElementById('tutorialNavToggle');
function openTutorialNav() {
tutorialRelated.classList.add('is-open');
overlay.classList.add('is-visible');
document.body.style.overflow = 'hidden';
}
function closeTutorialNav() {
tutorialRelated.classList.remove('is-open');
overlay.classList.remove('is-visible');
document.body.style.overflow = '';
}
tutorialNavToggle.addEventListener('click', openTutorialNav);
Das Zusammenspiel aller Komponenten
Lass uns das Ganze nochmal von Anfang bis Ende durchgehen:
Phase 1: Build-Zeit (npm run build)
generate-navigation.jswird ausgeführt- Es findet alle HTML-Dateien
- Für jede Datei:
- Findet h2/h3 Überschriften
- Generiert IDs und fügt sie zu Überschriften hinzu
- Erstellt TOC-Links für linke Sidebar
- Erstellt Tutorial-Liste für rechte Sidebar
- Schreibt alles ins HTML
- Ergebnis: HTML-Dateien mit fertigem Sidebar-HTML
Phase 2: Seite lädt im Browser
- Browser lädt HTML-Datei
- Beide Sidebars sind im HTML, aber nicht sichtbar (CSS:
transform: translateX(-100%)) - CSS-Dateien laden:
style.css→ Basis-Stylingsidebar.css→ Sidebar-Styling
- JavaScript-Dateien laden:
main.js→ Header, Theme Togglesidebar.js→ Sidebar-Interaktionenscroll-spy.js→ Active-Link-Tracking
Phase 3: User-Interaktion
User klickt auf Sidebar-Button
- Button-Click Event
sidebar.jsfügt.is-openKlasse hinzu- CSS Transition schiebt Sidebar ins Sichtfeld
- Overlay wird sichtbar
User scrollt auf der Seite
- Scroll Event
scroll-spy.jsprüft Scroll-Position- Findet aktive Überschrift
- Fügt
.activeKlasse zum passenden TOC-Link hinzu - CSS hebt den Link hervor
User klickt auf TOC-Link
- Link-Click Event
sidebar.jsverhindert Standard-Verhalten- Smooth Scroll zur Überschrift (mit 100px Offset)
- Sidebar schließt sich automatisch
scroll-spy.jsaktualisiert aktiven Link
CSS-Architektur
Die Sidebars nutzen moderne CSS-Features für Performance und Responsiveness:
Positionierung (Fixed)
.tutorial-sidebar {
position: fixed;
top: 0;
left: 0;
width: 280px;
height: 100vh;
/* Sidebar ist standardmäßig versteckt */
transform: translateX(-100%);
transition: transform 0.3s ease;
z-index: 1000;
}
Warum transform statt left?
transformnutzt GPU-Beschleunigung → butterweiche Animationlefttriggert Reflow → langsamer und ruckelig
Open-State
.tutorial-sidebar.is-open {
transform: translateX(0); /* Schiebt Sidebar ins Sichtfeld */
}
Responsive Design
/* Desktop: Sidebars dauerhaft sichtbar */
@media (min-width: 1200px) {
.tutorial-sidebar {
transform: translateX(0);
position: sticky; /* Statt fixed */
top: 100px;
}
.sidebar-toggle {
display: none; /* Buttons ausblenden */
}
}
Auf Desktop:
- Sidebars sind dauerhaft sichtbar
- Floating Buttons werden ausgeblendet
- Sidebars scrollen mit (
position: sticky)
Auf Mobile:
- Sidebars sind versteckt
- Floating Buttons sind sichtbar
- Sidebars öffnen als Overlay
Best Practices
1. IDs für Überschriften
Wenn du IDs manuell vergibst (statt sie generieren zu lassen):
- Nutze Kleinbuchstaben
- Nutze Bindestriche statt Leerzeichen
- Vermeide Sonderzeichen
- Beispiel:
id="was-ist-css"
2. no-toc Klasse
Nutze class="no-toc" für Überschriften, die NICHT im
Inhaltsverzeichnis erscheinen
sollen:
<h3 class="no-toc">Diese Überschrift erscheint nicht in der Sidebar</h3>
3. Tutorial-Reihenfolge
Halte TUTORIAL_ORDER immer aktuell:
- Neue Tutorials sofort eintragen
- Reihenfolge logisch gestalten (Basics → Advanced)
- Nach Änderungen
npm run buildausführen
4. Performance
Das System ist bereits optimiert:
- HTML wird beim Build generiert (nicht im Browser!)
- ScrollSpy nutzt Throttling
- CSS Transforms für Animationen
passive: truefür Scroll-Events
Häufige Probleme
Problem: Sidebar-Links führen nirgends hin
Ursache:
- IDs fehlen in den Überschriften
Lösung:
- Führe
npm run buildaus - Prüfe ob Überschriften jetzt IDs haben
- Falls nicht: Überprüfe
addIdsToHeadingsAndGenerateSidebar()
Problem: ScrollSpy funktioniert nicht
Ursache:
- Script nicht geladen oder IDs fehlen
Lösung:
- Prüfe ob
scroll-spy.jseingebunden ist - Öffne Browser-Konsole auf Fehler prüfen
- Prüfe ob Überschriften IDs haben
Problem: Rechte Sidebar zeigt falsche Tutorials
Ursache:
- Falsche Unterkategorie oder
TUTORIAL_ORDERfehlt
Lösung:
- Prüfe ob Unterkategorie in
TUTORIAL_ORDERexistiert - Prüfe Ordnerstruktur (z.B.
/tutorials/css/css-basics/) - Führe
npm run buildaus
Problem: Sidebar öffnet sich nicht
Ursache:
- JavaScript-Fehler oder falsche IDs
Lösung:
- Öffne Browser-Konsole
- Prüfe auf Fehler
- Prüfe ob
sidebar.jsgeladen ist - Prüfe HTML-IDs:
tutorialSidebar,sidebarToggle, etc.
Zusammenfassung
Das Sidebar-System ist ein Zusammenspiel mehrerer Komponenten:
- Generierung:
generate-navigation.js - Basis: h2/h3 Überschriften
- Features: ScrollSpy, Smooth Scroll
- Interaktion:
sidebar.js
- Generierung:
generate-navigation.js - Basis:
TUTORIAL_ORDER - Features: Active-Link
- Interaktion:
sidebar.js
- Beide Sidebars werden server-seitig generiert
- JavaScript ist nur für Interaktionen zuständig
- ScrollSpy tracked die Scroll-Position
- Alles ist responsive und performance-optimiert
Mehr aus DevPanicZone!
Tutorials werden geladen...