Warum ich meine eigene Table of Contents gebaut habe (und wie sie funktioniert)
Fast jede Dokumentation hat eine Table of Contents.
Und fast jede fühlt sich… irgendwie falsch an.
- Sprünge zwischen Sections
- Verzögerte Updates
- Kein echtes Gefühl, wo man sich gerade im Dokument befindet
Technisch liegt das meist daran, dass alles auf IntersectionObserver basiert.
Das funktioniert, aber es ist diskret statt kontinuierlich.
Du bist nicht irgendwo zwischen zwei Sections du bist einfach in einer.
Ziel: Eine ToC, die sich wie Scrollen anfühlt
Ich wollte etwas anderes:
Die Navigation soll sich genauso flüssig anfühlen wie der Scroll selbst.
Also nicht:
Section wird aktiv → UI springt
Sondern:
Position im Dokument → direkt auf UI gemappt
Die Idee
Die gesamte ToC wird als SVG Path dargestellt.
- Jeder Heading-Punkt wird auf eine Y-Position gemappt
- Daraus entsteht eine durchgehende Linie
- Die Scrollposition wird kontinuierlich darauf projiziert
Das Ergebnis:
- Ein Track, der die gesamte Struktur zeigt
- Ein Highlight, das genau den Viewport widerspiegelt
- Ein Dot, der entlang der Linie „fährt"
Architektur (vereinfacht)
Der entscheidende Punkt: Es gibt keine Events, keine Thresholds – nur ein kontinuierliches Mapping.
Warum kein IntersectionObserver?
IntersectionObserver beantwortet nur:
Ist Element X sichtbar, ja oder nein?
Aber nicht:
- Wie weit ist es sichtbar?
- Wo genau bin ich zwischen zwei Sections?
Das führt zu Flackern, ungenauen States und „sprunghafter" Navigation.
Ich wollte stattdessen: Pixel-genaue Kontrolle.
Die technische Umsetzung
1. SVG Path generieren
Aus den Heading-Positionen entsteht ein Path:
- Vertikale Linie
- Indent bei
h3 - Bezier-Kurven für saubere Übergänge
2. Viewport als Clip
Der sichtbare Bereich wird nicht berechnet, sondern geclippt.
- Hintergrund-Track = gesamte Struktur
- Vordergrund-Track = per
clipPathauf Viewport begrenzt
Das fühlt sich deutlich natürlicher an als „aktive Items togglen".
3. Dot auf dem Path
Und hier kam der Part, bei dem ich wirklich gestruggelt habe.
Ich hatte am Anfang mehrere Ansätze im Kopf:
- Prozentuale Mapping-Ansätze
- Direkte Line-Interpolation
- Scroll → Index Mapping
Alles hat sich irgendwie ungenau oder „off" angefühlt.
Ich habe ungelogen Stunden daran gesessen und iteriert, verworfen, neu gedacht.
Der finale Ansatz: Der Dot wird jedes Frame per requestAnimationFrame berechnet.
- Binary Search entlang des SVG Paths
- Position →
transform: translate(...)
Kein React State → keine Re-Renders. Das läuft komplett außerhalb von React.
Das war der Punkt, wo es sich plötzlich richtig angefühlt hat.
4. Headless Core
Die gesamte Logik steckt in:
useTocNavigation()Damit kannst du eigene UI, eigene Styles und eigene Interaction Patterns bauen.
Features im Überblick
- Scroll-synchronisierte SVG Navigation
- Viewport-basierter Highlight-Track
- Dot, der entlang der Struktur fährt
- Saubere Übergänge zwischen
h2undh3 - Layout-Modi: rechts, links oder inline
- Headless Hook für maximale Flexibilität
- Keine externen Dependencies
Beispiel
import { TocNav } from 'toc-nav'
<TocNav items={headings} containerWidth="56rem" />Das rendert:
- Mobile: collapsible ToC
- Desktop: fixe Sidebar
- Automatisch versteckt bei zu wenigen Headings
Wann das wirklich sinnvoll ist
Das Ding ist nicht für jede Seite gedacht.
Aber perfekt für:
- technische Dokumentation
- lange Blogposts
- MDX-basierte Seiten
- alles, wo Struktur wichtig ist
Open Source
Eigentlich war das nur für meinen Blog gedacht.
Aber ganz ehrlich: Es wäre Verschwendung gewesen, das nicht zu teilen.
Und das fühlt sich komplett anders an.
Einfach sauber.