How To Create Table of Contents In JavaScript

table-of-contents-using-javascript

Table of contents ( TOC ) is the list of all the headings in a blog post or an article with a link to each of these headings.

TOC is used to navigate faster to a particular heading in a long content webpage, by clicking a link of a toc it directly scrolls to that particular section of the page.

In this short tutorial, You are going to learn how to create Table Of Contents using core JavaScript.

  • Dynamic TOC
  • Without Any Library
  • Few lines of JS
  • Nested Lists
  • Smooth Scroll

Demo: The Toc displayed above is the real-time demo.

Ad

HTML Structure

The Html content for this tutorial is very simple. To better understand it we are going to create TOC for the current page that you are just currently reading.

Html Code

<div id="toc" role=”doc-toc”> <span id="toc-heading"> Table Of Contents </span> <input type="checkbox" id="toc-checkbox"> </div>
Code language: HTML, XML (xml)

We only need the h2 and h3 headings which are only inside the article tag, not the whole body.

<article id="single-article"> // Here is the actual Content. // It contains some Headings H2, H3 </article>
Code language: JavaScript (javascript)

It is boring to work with dummy text such as Lorem Ipsum, that is why we are going to create toc for the real web page.

This code is going to work with any type of webpage which has some text content in it.

Ad

JavaScript Code

/* ---Table of Contents Js ------ */ window.addEventListener('DOMContentLoaded', (event) => { const article = document.getElementById("single-article"); const headings = article.querySelectorAll("h2, h3"); const toc = document.getElementById("toc"); const totalHeadings = headings.length; let tocOl = document.createElement("ol"); let tocFragment = new DocumentFragment(); let mainLi = null; let subUl = null; let subLi = null; let isSibling = false; if(totalHeadings > 1) { for (let element of headings) { let anchor = document.createElement("a"); let anchorText = element.innerText; anchor.innerText = anchorText; let elementId = anchorText.replaceAll(" ", "-").toLowerCase(); anchor.href = "#" + elementId; element.id = elementId; let level = element.nodeName; if ("H3" === level) { if (mainLi) { subLi = document.createElement("li"); subLi.appendChild(anchor); if (isSibling === false) { subUl = document.createElement("ul"); } subUl.appendChild(subLi); mainLi.appendChild(subUl); isSibling = true; } } else { mainLi = document.createElement("li"); mainLi.appendChild(anchor); tocFragment.appendChild(mainLi); isSibling = false; subUl = null; } } tocOl.append(tocFragment); toc.append(tocOl); } else { toc.style.display = "none"; } });
Code language: JavaScript (javascript)

Take a brief look at the following steps.

  • Add Event Listener DomContentLoaded.
  • Get all headings, using querySelectorAll().
  • Create OL
  • Loop over all headings and set Id.
  • Create Anchor
  • Check the heading level.
  • Create a List of all headings.

Add Event Listener DomContentLoaded

To interact with DOM, all the Html content needs to be loaded first, that is why we are adding an event listener (‘DOMContentLoaded’) which fires only after all the Html is loaded in the browser.

window.addEventListener('DOMContentLoaded', (event) => { // .. Actual code here } );
Code language: JavaScript (javascript)

Note: You can skip this part if you are using an external javascript file with the attribute defer or inline script before closing the body tag.

Get all headings

The next step is to get the Html content which includes all the text and headings of the page.

Usually, the content is always inside the Article tag (<article>).

const article = document.getElementById(‘article’);
Code language: JavaScript (javascript)

Now get all the headings inside the article tag. For the sake of simplicity we are selecting only H2, and H3.

Ad
const headings = article.querySelectorAll("h2, h3");
Code language: JavaScript (javascript)

Store the total headings in a variable.

const totalHeadings = headings.length;
Code language: JavaScript (javascript)

In our Html, there is a div with id toc already present in the article tag. But you can also create it using js.

const toc = document.getElementById("toc");
Code language: JavaScript (javascript)

Create OL

Now create the main OL which will contain all the main list items.

let tocOl = document.createElement("ol");
Code language: JavaScript (javascript)

Create a document fragment, which is used as a container for the ol. All the list items will be appended to it.

What the document fragment does here is it makes a virtual dom whenever we add something to it. It is used for the performance of the script because it does not alter the actual dom elements.

let tocFragment = new DocumentFragment();
Code language: JavaScript (javascript)

Now create a variable for the main li items.

let mainLi = null;
Code language: JavaScript (javascript)

Also, add some more variables which will be used in the sub ul.

let subUl = null; let subLi = null; let isSibling = false;
Code language: JavaScript (javascript)

Loop Over Headings

First, check if there are more than 1 headings in the variable headings.

If the condition is false we need to hide the toc div.

if (totalHeadings > 1) { // Loop will be inside } else { toc.style.display = "none"; }
Code language: JavaScript (javascript)

Start the loop and iterate over all the headings.

if (totalHeadings > 1) { for (let element of headings) { // Loop body } }
Code language: JavaScript (javascript)

Here the for loop with get all the headings one by one and will assign every heading node to the variable element.

Create Anchor

Now create an anchor tag.

let anchor = document.createElement("a");
Code language: JavaScript (javascript)

Put the text of the current heading into the anchor text of the above link tag.

let anchorText = element.innerText; anchor.innerText = anchorText;
Code language: JavaScript (javascript)

Now make the anchor text URL friendly, by replacing all the spaces with hyphens and by changing it to lowercase.

Store the URL in the variable elementId.

let elementId = anchorText.replaceAll(" ", "-").toLowerCase();
Code language: JavaScript (javascript)

Also, assign the above URL to the previously created anchor tag to make it a hyperlink.

anchor.href = "#" + elementId;
Code language: JavaScript (javascript)

The last step now is to add the id to the actual heading element.

element.id = elementId;

Make a variable that is used to check the current heading level inside the loop.

let level = element.nodeName;
Code language: JavaScript (javascript)

Check the level

In every iteration, we need the check the level of the heading.

Inside the if condition we need another if condition to check if there already exists a main h2 li item.

Since we have to put h3 inside h2, so if there is no h2 we need to skip this h3 heading then.

if ("H3" === level) { if (mainLi) { // create ul inside li } } else // create main li items }
Code language: JavaScript (javascript)

Therefore, if the condition is true, we have to create a new sub-list inside the previous li item.

Create a new li item and append an anchor tag to it.

subLi = document.createElement("li"); subLi.appendChild(anchor);
Code language: JavaScript (javascript)

Remember the variable isSibling which is false by default. Its value changes to true only if there is h3 inside the loop.

If there are more than one h3 tags after the h2 tag we do not have to create a separate ul inside the li, all we need is to put all those h3 tags inside the same nested ul and append it to the main li item of the h2 tag.

if (isSibling === false) { subUl = document.createElement("ul"); }
Code language: JavaScript (javascript)

So if there is no h3 tag created till now its value is false, otherwise if there is already a nested list created ( ul inside li)  then its value is true which means that this tag is going to be the sibling of the previously created nested li items.

Create The Main List

If the heading level is h2, not h3, then the code of the else part will run and execute.

Here we need to make the main list of items.

if ( “H3” === level ) { //make sub list } else { mainLi = document.createElement("li"); mainLi.appendChild(anchor); tocFragment.appendChild(mainLi); isSibling = false; subUl = null; }
Code language: JavaScript (javascript)

Now the final step is to add the fragment to the ol and add the final lists to the body of the page.

tocOl.append(tocFragment); toc.append(tocOl);
Code language: CSS (css)

CSS for TOC

The table of contents is now complete all we need is to add some style to the elements.

#toc { background-color: #f1f1f1; width: max-content; max-width: 95%; min-height: 9rem; margin: 1rem auto; padding: 0.5rem; outline: 1px solid #ddd; position: relative; border-radius: 0.4rem; } #toc-heading { display: inline-block; font-size: 1rem; color: var(--heading-font-color); margin-bottom: 0.5rem; margin-right: 2rem; } #toc #toc-checkbox { display: inline-block; position: absolute; top: 0.8rem; right: 1rem; width: 1rem; height: 1rem; opacity: 0.4; } #toc #toc-checkbox:hover { cursor: pointer; opacity: 0.7; } #toc input:checked ~ ol { display: none; } #toc ol { border-top: 1px solid #ddd; list-style-position: inside; padding: 0.5rem 0.3rem; line-height: 1.8; } #toc ol ul { margin-left: 1.8rem; list-style-position: inside; list-style-type: disc; line-height: 1.5; } #toc li::marker { color: #c8c8c8; } #toc a { font-size: 1.1rem; padding-left: 0.4rem; } #toc ul a { padding: 0; font-size: 1rem; } @media (min-width: 900px ) { #toc { max-width: 70%; margin: 1.5rem auto; box-shadow: 4px 4px 6px 6px #fafcff, -4px -4px 6px 6px #fafcff; } }
Code language: CSS (css)

Smooth Scroll

You can also add a smooth scroll to the Html tag so that when a user clicks on any link, the browser navigates smoothly without a rapid jump to that section.

html { scroll-behavior: smooth; // add some space from top scroll-padding-top: 5rem; }
Code language: JavaScript (javascript)

Thank You!

Ad