Search for the Article

Table of Contents : Generating Automatic Table of Contents With HTML + CSS + JQUERY

Table of contents is a feature in which you can see the list of all headings and subheadings in a document.

What is table of content ?

The table of contents provides a quick overview of the key points in your document without having to read through the entire document. It is abbreviated as ToC and labeled with numbers or icons. It is generally placed at the beginning of a document..
Long articles and blog posts are often intimidating to read. Organizing the content with a table of contents can help readers feel more comfortable with the length of the article, and may even encourage them to continue reading.

Uses of table of content for your blog/page

  1. It is helpful for readers who want to find specific information without having to read through the entire document.
  2. This is especially helpful if your document has is very long or several sections or contains numerous topics.
  3. Summarizes the main content without looking crowded.
  4. It helps cotent in ranking.

Let's Create Table of Contents (HTML + CSS + JQUERY)

Step 1: HTML

Let’s create an HTML for Table of content:


<nav id="toc"></nav> 
<main> 
 
//* Just add content inside this tag  *//
 
</main> 


Step 2: CSS

Now add some CSS style to give it a height and width as well as the background color sky blue.


#toc ul{
 background-color: #cfe9f9;
 padding: 5px 0px 5px 20px;
}
#toc ul li{
  list-style: disclosure-open;
}

#toc a{
text-decoration: none;
}


#toc > ul{
  border-top: 8px solid #4d2626;
}



Step 3: Javascript Code

Now, add some JavaScript code that is given below:


let tocId = "toc";

let headings;
let headingIds = [];
let headingIntersectionData = {};
let headerObserver;

function setLinkActive(link) {
  const links = document.querySelectorAll(`#${tocId} a`);
  links.forEach((link) => link.classList.remove("active"));
  if (link) {
    link.classList.add("active");
  }
}

function getProperListSection(heading, previousHeading, currentListElement) {
  let listSection = currentListElement;
  if (previousHeading) {
    if (heading.tagName.slice(-1) > previousHeading.tagName.slice(-1)) {
      let nextSection = document.createElement("ul");
      listSection.appendChild(nextSection);
      return nextSection;
    } else if (heading.tagName.slice(-1) < previousHeading.tagName.slice(-1)) {
      let indentationDiff =
        parseInt(previousHeading.tagName.slice(-1)) -
        parseInt(heading.tagName.slice(-1));
      while (indentationDiff > 0) {
        listSection = listSection.parentElement;
        indentationDiff--;
      }
    }
  }
  return listSection;
}

function setIdFromContent(element, appendedId) {
  if (!element.id) {
    element.id = `${element.innerHTML
      .replace(/:/g, "")
      .trim()
      .toLowerCase()
      .split(" ")
      .join("-")}-${appendedId}`;
  }
}

function addNavigationLinkForHeading(heading, currentSectionList) {
  let listItem = document.createElement("li");
  let anchor = document.createElement("a");
  anchor.innerHTML = heading.innerHTML;
  anchor.id = `${heading.id}-link`;
  anchor.href = `#${heading.id}`;
  anchor.onclick = (e) => {
    setTimeout(() => {
      setLinkActive(anchor);
    });
  };
  listItem.appendChild(anchor);
  currentSectionList.appendChild(listItem);
}

function buildTableOfContentsFromHeadings() {
  const tocElement = document.querySelector(`#${tocId}`);
  const main = document.querySelector("main");
  if (!main) {
    throw Error("A `main` tag section is required to query headings from.");
  }
  headings = main.querySelectorAll("h1, h2, h3, h4, h5, h6");
  let previousHeading;
  let currentSectionList = document.createElement("ul");
  tocElement.appendChild(currentSectionList);

  headings.forEach((heading, index) => {
    currentSectionList = getProperListSection(
      heading,
      previousHeading,
      currentSectionList
    );
    setIdFromContent(heading, index);
    addNavigationLinkForHeading(heading, currentSectionList);

    headingIds.push(heading.id);
    headingIntersectionData[heading.id] = {
      y: 0
    };
    previousHeading = heading;
  });
}

function updateActiveHeadingOnIntersection(entry) {
  const previousY = headingIntersectionData[entry.target.id].y;
  const currentY = entry.boundingClientRect.y;
  const id = `#${entry.target.id}`;
  const link = document.querySelector(id + "-link");
  const index = headingIds.indexOf(entry.target.id);

  if (entry.isIntersecting) {
    if (currentY > previousY && index !== 0) {
      console.log(id + ":1 enter top");
    } else {
      console.log(id + ":2 enter bottom");
      setLinkActive(link);
    }
  } else {
    if (currentY > previousY) {
      console.log(id + ":3 leave bottom");
      const lastLink = document.querySelector(`#${headingIds[index - 1]}-link`);
      setLinkActive(lastLink);
    } else {
      console.log(id + ":4 leave top");
    }
  }

  headingIntersectionData[entry.target.id].y = currentY;
}

function observeHeadings() {
  let options = {
    root: document.querySelector("main"),
    threshold: 0.1
  };
  headerObserver = new IntersectionObserver(
    (entries) => entries.forEach(updateActiveHeadingOnIntersection),
    options
  );
  Array.from(headings)
    .reverse()
    .forEach((heading) => headerObserver.observe(heading));
}

window.addEventListener("load", (event) => {
  buildTableOfContentsFromHeadings();
  if ("IntersectionObserver" in window) {
    observeHeadings();
  }
});

window.addEventListener("unload", (event) => {
  headerObserver.disconnect();
});



Hope this help ! If you need customization or faceing nay implementation issue , please let me know though form.