WordPress Inhaltsverzeichnis erstellen (ohne Plugin, mein kostenloser Code)

Ein Inhaltsverzeichnis für WordPress Beiträge und Artikel ist eine geschickte Sache. Besucher haben einen schnellen Überblick, SEO-Vorteile und schickes Design werten deinen Beitrag so richtig auf!

Es ist einfach zu umständlich ein Inhaltsverzeichnis in jedem Beitrag neu zu erstellen oder bei Änderungen zu aktualisieren. Für so eine „Kleinigkeit“ ein Plugin zu installieren ist auch keine gute Lösung, da es die Seite nur langsamer macht und man es – wie Du gleich sehen wirst – auch ganz einfach selber coden kann.

Falls Du keine oder wenig Programmiererfahrung hast, kannst Du dieses Inhaltsverzeichnis trotzdem verwenden. Das ist kein Problem! Falls noch Fragen offen bleiben, kann ich auch über die Kommentare versuchen Hilfestellung zu geben.

So wird unser Endergebnis aussehen:

Den kompletten Code habe ich selbst programmiert und darf kostenlos für jegliche Zwecke (auch kommerziell) verwendet und auch nach belieben verändert werden!

Schritt 1: Inhaltsverzeichnis generieren (PHP Skript)

Für Programmierunerfahrene: Im Backend-Menü (linker Rand) gibt es einen Punkt „Design“. Navigiere dort zum Unterpunkt „Theme-Editor“ und wähle dann am rechten Rand unter „Theme-Dateien“ die „Theme-Funktionen“ (functions.php) aus. Dort kannst Du bis ganz ans Ende scrollen und nach der letzten Zeile Code 2-3 Leerzeilen einfügen und dann den untenstehenden PHP-Code 1:1 einfügen und abspeichern.

Für Programmiererfahrene: Dieser Code muss in die functions.php des verwendeten Themes (falls verwendet ins Child-Theme) eingefügt werden.

function get_toc($content) {
	// get headlines
	$headings = get_headings($content, 1, 6);

	// parse toc
	ob_start();
	echo "<div class='table-of-contents'>";
	echo "<span class='toc-headline'>Table Of Contents</span>";
	echo "<!-- Table of contents by webdeasy.de -->";
	echo "<span class='toggle-toc custom-setting' title='collapse'>−</span>";
	parse_toc($headings, 0, 0);
	echo "</div>";
	return ob_get_clean();
}

function parse_toc($headings, $index, $recursive_counter) {
  // prevent errors

  if($recursive_counter > 60 || !count($headings)) return;

  // get all needed elements
  $last_element = $index > 0 ? $headings[$index - 1] : NULL;
  $current_element = $headings[$index];
  $next_element = NULL;
  if($index < count($headings) && isset($headings[$index + 1])) {
    $next_element = $headings[$index + 1];
  }

  // end recursive calls
  if($current_element == NULL) return;

  // get all needed variables
  $tag = intval($headings[$index]["tag"]);
  $id = $headings[$index]["id"];
  $classes = isset($headings[$index]["classes"]) ? $headings[$index]["classes"] : array();
  $name = $headings[$index]["name"];

  // element not in toc
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("nitoc", $current_element["classes"])) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
    return;
  }

  // parse toc begin or toc subpart begin
  if($last_element == NULL) echo "<ul>";
  if($last_element != NULL && $last_element["tag"] < $tag) {
    for($i = 0; $i < $tag - $last_element["tag"]; $i++) {
      echo "<ul>";
    }
  }

  // build li class
  $li_classes = "";
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("toc-bold", $current_element["classes"])) $li_classes = " class='bold'";

  // parse line begin
  echo "<li" . $li_classes .">";

  // only parse name, when li is not bold
  if(isset($current_element["classes"]) && $current_element["classes"] && in_array("toc-bold", $current_element["classes"])) {
    echo $name;
  } else {
    echo "<a href='#" . $id . "'>" . $name . "</a>";
  }
  if($next_element && intval($next_element["tag"]) > $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }

  // parse line end
  echo "</li>";

  // parse next line
  if($next_element && intval($next_element["tag"]) == $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }

  // parse toc end or toc subpart end
  if ($next_element == NULL || ($next_element && $next_element["tag"] < $tag)) {
    echo "</ul>";
    if ($next_element && $tag - intval($next_element["tag"]) >= 2) {
      echo "</li>";
      for($i = 1; $i < $tag - intval($next_element["tag"]); $i++) {
        echo "</ul>";
      }
    }
  }

  // parse top subpart
  if($next_element != NULL && $next_element["tag"] < $tag) {
    parse_toc($headings, $index + 1, $recursive_counter + 1);
  }
}

function get_headings($content, $from_tag = 1, $to_tag = 6) {
  $headings = array();
  preg_match_all("/<h([" . $from_tag . "-" . $to_tag . "])([^<]*)>(.*)<\/h[" . $from_tag . "-" . $to_tag . "]>/", $content, $matches);
  
  for($i = 0; $i < count($matches[1]); $i++) {
    $headings[$i]["tag"] = $matches[1][$i];
    // get id
    $att_string = $matches[2][$i];
    preg_match("/id=\"([^\"]*)\"/", $att_string , $id_matches);
    $headings[$i]["id"] = $id_matches[1];
    // get classes
    $att_string = $matches[2][$i];
    preg_match_all("/class=\"([^\"]*)\"/", $att_string , $class_matches);
    for($j = 0; $j < count($class_matches[1]); $j++) {
      $headings[$i]["classes"] = explode(" ", $class_matches[1][$j]);
    }
    $headings[$i]["name"] = strip_tags($matches[3][$i]);
  }
  return $headings;
}

Mit diesem Code werden alle Überschriften ausgelesen, zusammengefasst und als Inhaltsverzeichnis wieder ausgegeben.

Standardmäßig werden alle Überschriften (H1-H6) ausgelesen und ausgegeben. Wenn du das anpassen möchtest, kannst du die Parameter in Zeile 3 anpassen.

Inhaltsverzeichnis in Seiten einfügen

Du kannst das Inhaltsverzeichnis in zwei verschiedenen Wegen in Deine Posts einbinden.

Möglichkeit 1: Wenn Du die Position in jedem Beitrag über den Shortcode TOC selber festlegen möchtest, dann fügen noch zusätzlich diesen Code ein:

// TOC (from webdeasy.de)
function toc_shortcode() {
    return get_toc(get_the_content());
}
add_shortcode('TOC', 'toc_shortcode');

Möglichkeit 2: Wenn das Inhaltsverzeichnis automatisch nach dem ersten Absatz in jedem Beitrag eingefügt werden soll, dann füge noch zusätzlich diesen Code hinzu:

function add_table_of_content($content) {
	if (!is_single()) return $content;

    $paragraphs = explode("</p>", $content);
    $paragraphs_count = count($paragraphs);
    $middle_index= absint(floor($paragraphs_count / 2));
    $new_content = '';

    for ($i = 0; $i < $paragraphs_count; $i++) {
        if ($i === 1) {
        	$new_content .= get_toc($content);
        }

        $new_content .= $paragraphs[$i] . "</p>";
    }
    return $new_content;
}
// add our table of contents filter (from webdeasy.de)
add_filter('the_content', 'add_table_of_content');

Solltest Du das Inhaltsverzeichnis erst nach dem zweiten Absatz ausgeben lassen wollen, kannst Du einfach die Zahl in Zeile 10 anpassen.

Möchtest Du mit deiner Website oder deinem Blog Geld verdienen? Dann schau dir mein Erfahrungsbericht über Ezoic an, die beste Google AdSense Alternative!

Schritt 2: Inhaltsverzeichnis stylen (CSS)

Mit dem bisherigen Code wird Dein Inhaltsverzeichnis bereits generiert. Allerdings würde es als normale Liste <ul> ausgegeben werden. Damit das Ganze etwas schöner aussieht, spendieren wir dazu auch noch ein paar Zeilen CSS.

Für Programmiererfahrene: Dieser Code muss in eine CSS Datei des verwendeten Themes (falls verwendet ins Child-Theme) eingefügt werden. Oft ist das Standardmäßig die style.css auf oberster Ebene im Theme-Ordner.

Für Programmierunerfahrene: Im gleichen Menüpunkt wie schon beim PHP-Code kannst Du einfach rechts „Stylesheet“ (style.css) auswählen. Dort gehst Du auch wieder ans Ende und fügst nach der letzten Zeile den folgenden Code ein und speicherst die Datei dann ab.

Ich habe zwei verschiedene Styles erstellt. Nutze einfach den Code vom Inhaltsverzeichnis, welches dir am besten gefällt.

Variante 1 – Minimalistic

CSS-Code dazu:

/* Adjust these variables for your project */
:root {
	--dark-grey: #333333;
	--main-color: #036773;
	--font-size: 16px;
	--line-height: 1.2;
}

.table-of-contents {
	margin: 4rem 0;
	position: relative;
}

.table-of-contents .toc-headline {
	font-size: 22px;
	color: var(--dark-grey);
	font-weight: 600;
	display: block;
	cursor: pointer;
}

.table-of-contents .toggle-toc {
	position: absolute;
	top: 0;
	right: 1rem;
	font-size: 30px;
	cursor: pointer;
	font-weight: 800;
	color: var(--main-color);
}

.table-of-contents ul {
	padding: 0;
	padding-left: 1rem;
}

.table-of-contents li {
	position: relative;
	padding-left: 1rem;
	list-style: none;
	line-height: var(--line-height);
	font-weight: 400;
	margin: .3rem 0;
	transition: .2s ease all;
}

.table-of-contents li:hover {
	padding-left: 1.1rem;
}

.table-of-contents li a {
	font-size: var(--font-size);
	line-height: var(--line-height);
	text-decoration: none;
	color: var(--main-color);
}

.table-of-contents li:before {
	content: '';
	-webkit-transform: translateX(-0.75em) rotate(45deg);
	transform: translateX(-0.75em) rotate(45deg);
	height: 0.5em;
	width: 0.5em;
	border-top: solid 2px var(--dark-grey);
	border-right: solid 2px var(--dark-grey);
	border-radius: 0;
	background: 0 0;
	position: absolute;
	display: block;
	top: 7px;
	left: 0;
}

.table-of-contents li>ul {
	padding-left: 0.7rem;
	padding-bottom: 1rem;
}

Variante 2 – Basic

CSS-Code dazu:

/* Adjust these variables for your project */
:root {
	--dark-grey: #333333;
  --background-color: #eee;
	--main-color: #036773;
	--font-size: 16px;
	--line-height: 1.2;
}

.table-of-contents {
	margin: 4rem 0;
	position: relative;
  background-color: var(--background-color);
  padding: .5rem 1rem;
  border-left: 5px solid var(--main-color);
  border-radius: 5px;
}

.table-of-contents .toc-headline {
	font-size: 22px;
	color: var(--dark-grey);
	font-weight: 600;
	display: block;
	cursor: pointer;
  margin-top: .3rem;
}

.table-of-contents .toggle-toc {
	position: absolute;
	top: .8rem;
	right: .8rem;
	font-size: 20px;
	cursor: pointer;
	font-weight: 800;
	color: #FFF;
  width: 1.5rem;
  height: 1.5rem;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
  line-height: 10px;
  background-color: var(--main-color);
}

.table-of-contents ul {
	padding: 0;
}

.table-of-contents li {
	position: relative;
	list-style: none;
	line-height: var(--line-height);
	font-weight: 400;
	margin: .3rem 0;
	transition: .2s ease all;
}

.table-of-contents li a {
	font-size: var(--font-size);
	line-height: var(--line-height);
	color: var(--main-color);
}

.table-of-contents li>ul {
	padding-left: 1rem;
	padding-bottom: .5rem;
}

Damit sieht das Inhaltsverzeichnis nun besser aus und ist auch als solches erkennbar. Das kannst Du natürlich alles an Dein Theme und Deinen Wünschen entsprechend anpassen (lassen).

Schritt 3: Inhaltsverzeichnis auf- und zuklappen (JavaScript)

Mit diesem Code können Besucher das Inhaltsverzeichnis auf- und zuklappen.

document.querySelectorAll('.table-of-contents .toggle-toc, .table-of-contents .toc-headline').forEach(toggler => {
	toggler.addEventListener('click', function() {
		let tocList = document.querySelectorAll('.table-of-contents ul')[0];
		let toggler = document.querySelectorAll('.table-of-contents .toggle-toc')[0];
		if(tocList.style.display == 'none') {
			tocList.style.display = 'block';
			toggler.innerHTML = '-';
		} else {
			tocList.style.display = 'none';
			toggler.innerHTML = '+';
		}
	});
});

Für Programmiererfahrene: Das kannst Du einfach in eine bestehende JS-Datei einfügen.

Für Programmierunerfahrene: Im Theme-Editor gibt es meist einen js Ordner mit .js Dateien oder direkt eine Datei mit der Endung .js. Wähle einfach eine Datei aus und füge den Code ganz am Ende ein.

Inhaltsverzeichnis standardmäßig schließen

Falls das Inhaltsverzeichnis standardmäßig geschlossen sein soll, kannst du noch die folgende Zeile nach dem vorherigen JavaScript Code ergänzen.

document.querySelectorAll('.table-of-contents .toggle-toc, .table-of-contents .toc-headline')[0].click();

Schritt 4: Verzeichnis in einzelne Beiträge einbinden

Wenn Du Dich für Möglichkeit 1 entschieden hast, musst Du, um das Inhaltsverzeichnis nun auf einer Seite auszugeben, einfach im Backendeditor des jeweiligen Beitrags den Shortcode TOC einfügen.

TOC Shortcode in WordPress-Beitrag einfügen
TOC Shortcode in WordPress-Beitrag einfügen

Damit die einzelnen Überschriften auch verlinkt sind, benötigen diese einen sogenannten HTML-Anker. Auch hier gibt es wieder zwei Möglichkeiten. Entweder ein Skript fügt sie für Dich automatisch ein (Möglichkeit 1) oder Du fügst sie manuell ein (Möglichkeit 2).

Möglichkeit 1:

Das Skript hat mir ein Leser empfohlen (vielen Dank!) und er scheint es von dieser Seite zu haben.

Beachte, dass Du diesen Code vor den PHP Code aus Punkt 1 setzt.

/**
 * Automatically add IDs to headings such as <h2></h2>
 */
function auto_id_headings( $content ) {
	$content = preg_replace_callback('/(\<h[1-6](.*?))\>(.*)(<\/h[1-6]>)/i', function( $matches ) {
		if(!stripos($matches[0], 'id=')) {
			$matches[0] = $matches[1] . $matches[2] . ' id="' . sanitize_title( $matches[3] ) . '">' . $matches[3] . $matches[4];
		}
		return $matches[0];
	}, $content);
    return $content;

}
add_filter('the_content', 'auto_id_headings');

Wenn du das Einfügen per Shortcode gewählt hast, musst du die Funktion toc_shortcode() aus Schritt 1 nochmal anpassen:

// TOC (from webdeasy.de)
function toc_shortcode() {
    return get_toc(auto_id_headings(get_the_content()));
}
add_shortcode('TOC', 'toc_shortcode');

Möglichkeit 2:

Um die Anker manuell einzufügen, klickst Du die Überschrift an und fügst unter „Erweitert“ einen Anker ein. Den Namen kannst Du Dir frei überlegen, sollte aber zur Überschrift passen.

WordPress HTML-Anker hinzufügen
WordPress HTML-Anker hinzufügen

Das ist alles! Wenn Du Dir die Seite jetzt anschaust, sollte das Inhaltsverzeichnis angezeigt werden.

Falls Du ein oder mehrere Menüpunkte ausschließen möchtest (wie diese Überschrift :)), so dass sie nicht im Inhaltsverzeichnis angezeigt werden, kannst Du der jeweiligen Überschrift zusätzlich die CSS Klasse nitoc geben. Das Skript ignoriert diese dann.

Fazit

Du hast es geschafft! Durch ein paar Zeilen Code kannst Du Dir jetzt immer Dein eigenes Inhaltsverzeichnis generieren lassen und musst nicht mehr auf fertige Plugins zurückgreifen. Oder noch schlimmer: die Inhaltsverzeichnisse immer selber schreiben und aktualisieren. Viel Spaß damit! 🙂

Ähnliche Beiträge
Beteilige dich an der Unterhaltung

12 Kommentare

  1. florian sagt:

    Hey danke für die super Anleitung.Bei mir funktioniert das Auf- und Zuklappen des Inhaltsverzeichnisses nicht.Woran könnte das liegen?

    1. Lorenz sagt:

      Kann viele Gründe haben:

      • schau mal nach, ob der JS Code auch wirklich geladen wird
      • schau in die Browser Konsole ob dort ein Fehler angezeigt wird
      Das wären die ersten beiden Sachen, die du überprüfen kannst. 

  2. Patrick sagt:

    Hallo, vielen Dank für die Super Anleitung!Ich würde gerne bei allen Beiträgen (Post) das Inhaltsverzeichnis automatisch einfügen und bei allen Seiten (Page) an der Stelle, die ich vorgesehen habe. Ist das auch möglich?Liebe Grüße, Patrick

    1. Lorenz sagt:

      Hi, freut mich! 🙂 Das geht natürlich auch:

      Füge dazu den Code für den Shortcode (toc_shortcode()) ein, damit kannst du dann den Shortcode bei allen Pages manuell einfügen.

      Für das automatische Einfügen auf Post Seiten nimmst du die add_table_of_content() Funktion und fügst sie auch mit ein. Die zweite Zeile der Funktion (if (!is_single()) return $content;) ist schon dafür zuständig, dass der TOC nur auf Post-Seiten angezeigt wird.

      Hoffe das hilft, falls weitere Fragen sind meld dich gern! 🙂

      1. Patrick sagt:

        Vielen Dank für deine Hilfe!

  3. Michael Sporleder sagt:

    Hallo Zusammen !

    Das geht aber noch eleganter mit einer rekursiven Lösung (bis h5):

    // filter function to generate the table of content
    function add_table_of_content_sub($tags, $ids, $names, $ii, $level) {
    $heads = array(‚h2‘, ‚h3‘, ‚h4‘, ‚h5‘);
    $ul = 0;
    $i = $ii;

    if( $level > 3) return $ii – 1;

    for(; $i 0 && $ul == 1 ) echo „\n“;
    return add_table_of_content_sub($tags, $ids, $names, $i, $level+1);
    }

    if( $level > 0 && $ul == 0 ) {
    echo „\n“;
    $ul = 1;
    }

    if ( empty($names[$i]) ) continue;

    echo „\n“;

    if(!empty($ids[$i])) {
    echo „„. $names[$i] . „\n“;
    } else {
    echo $names[$i];
    }

    $i = add_table_of_content_sub($tags, $ids, $names, $i+1, $level+1);

    echo „\n“;
    }

    if( $level > 0 && $ul == 1 ) echo „\n“;

    return $i;
    }

    function add_table_of_content($content) {
    ob_start();
    preg_match_all(„/(.*)/“, $content, $matches);
    $tags = $matches[0];
    $ids = $matches[1];
    $names = $matches[2];
    ?>
    Inhalt

    <?PHP
    return str_replace("{{TABLE_OF_CONTENTS}}“, ob_get_clean(), $content);
    }

    Viele Grüße

    Michael

    1. Lorenz sagt:

      Hi, super Lösung! Ich hatte bereits an einer rekursiven Version gearbeitet, ist allerdings noch nicht fertig geworden, daher noch die iterative Lösung. Sobald meine Version vollendet ist, werde ich sie hier auch veröffentlichen. Trotzdem vielen Dank, dass Du Deine Lösung hier teilst! 🙂

      Viele Grüße
      LH

      1. Sascha sagt:

        Hi, mich würde deine rekursive Lösung interessieren, sofern sie von jener abweicht, die Michael netterweise gepostet hat.Da ich nicht weiß, ob ich über eine Antwort informiert werde, würde ich mich über eine kurze Email sehr freuen, wär das möglich?Vielen Dank im Voraus 🙏Sascha

        1. Lorenz sagt:

          Ich habe es mir aufgeschrieben, sobald der Artikel geupdatet wirst, werde ich dich informieren 🙂

          Viele Grüße
          Lorenz

        2. Lorenz sagt:

          Der Beitrag ist jetzt überarbeitet – mit einer rekursiven Lösung! 🙂

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

bold italic underline strikeThrough
insertOrderedList insertUnorderedList outdent indent
removeFormat
createLink unlink
code

Das könnte dich auch interessieren