Is there a JavaScript solution to generating a “table of contents” for a page?

后端 未结 12 1712
广开言路
广开言路 2020-12-12 17:21

I have headers in

through
tags. Is there a way that I can use JavaScript to generate a table of contents for the contents tha

相关标签:
12条回答
  • 2020-12-12 18:04

    I really love d13's answer, but would like to improve it slightly to use html5 notations and keep a header's existing id:

    document.addEventListener('DOMContentLoaded', function() {
        htmlTableOfContents();
    } );                        
    
    function htmlTableOfContents( documentRef ) {
        var documentRef = documentRef || document;
        var toc = documentRef.getElementById("toc");
    //  Use headings inside <article> only:
    //  var headings = [].slice.call(documentRef.body.querySelectorAll('article h1, article h2, article h3, article h4, article h5, article h6'));
        var headings = [].slice.call(documentRef.body.querySelectorAll('h1, h2, h3, h4, h5, h6'));
        headings.forEach(function (heading, index) {
            var ref = "toc" + index;
            if ( heading.hasAttribute( "id" ) ) 
                ref = heading.getAttribute( "id" );
            else
                heading.setAttribute( "id", ref );
    
            var link = documentRef.createElement( "a" );
            link.setAttribute( "href", "#"+ ref );
            link.textContent = heading.textContent;
    
            var div = documentRef.createElement( "div" );
            div.setAttribute( "class", heading.tagName.toLowerCase() );
            div.appendChild( link );
            toc.appendChild( div );
        });
    }
    
    try {
        module.exports = htmlTableOfContents;
    } catch (e) {
        // module.exports is not defined
    }
    

    You use it by including the script in your header.

    The awesome thing is, you can use stylesheets for your table of contents:

    <style>
        #toc div.h1 { margin-left: 0 }
        #toc div.h2 { margin-left: 1em }
        #toc div.h3 { margin-left: 2em }
        #toc div.h4 { margin-left: 3em }
    </style>
    

    In my personal script, I use a slightly different selector:

    var headings = [].slice.call(documentRef.body.querySelectorAll("article h1, article h2, article h3, article h4, article h5, h6"));
    

    The main page is kept inside <article></article> and the script will search for headings inside the main article only. I can use a heading inside the table of contents, like <nav id="toc"><h3>Table of contents</h3></nav> or in a footer/header without it showing up inside the toc.

    In the comments below Gabriel Hautclocq has optimizations of the article code. He also points out, this is not strictly a list but a list-like div structure, which is simpler and generates shorter code.

    0 讨论(0)
  • 2020-12-12 18:11

    I couldn't resist putting together a quick implementation.

    Add the following script anywhere on your page:

    window.onload = function () {
        var toc = "";
        var level = 0;
    
        document.getElementById("contents").innerHTML =
            document.getElementById("contents").innerHTML.replace(
                /<h([\d])>([^<]+)<\/h([\d])>/gi,
                function (str, openLevel, titleText, closeLevel) {
                    if (openLevel != closeLevel) {
                        return str;
                    }
    
                    if (openLevel > level) {
                        toc += (new Array(openLevel - level + 1)).join("<ul>");
                    } else if (openLevel < level) {
                        toc += (new Array(level - openLevel + 1)).join("</ul>");
                    }
    
                    level = parseInt(openLevel);
    
                    var anchor = titleText.replace(/ /g, "_");
                    toc += "<li><a href=\"#" + anchor + "\">" + titleText
                        + "</a></li>";
    
                    return "<h" + openLevel + "><a name=\"" + anchor + "\">"
                        + titleText + "</a></h" + closeLevel + ">";
                }
            );
    
        if (level) {
            toc += (new Array(level + 1)).join("</ul>");
        }
    
        document.getElementById("toc").innerHTML += toc;
    };
    

    Your page should be structured something like this:

    <body>
        <div id="toc">
            <h3>Table of Contents</h3>
        </div>
        <hr/>
        <div id="contents">
            <h1>Fruits</h1>
            <h2>Red Fruits</h2>
            <h3>Apple</h3>
            <h3>Raspberry</h3>
            <h2>Orange Fruits</h2>
            <h3>Orange</h3>
            <h3>Tangerine</h3>
            <h1>Vegetables</h1>
            <h2>Vegetables Which Are Actually Fruits</h2>
            <h3>Tomato</h3>
            <h3>Eggplant</h3>
        </div>
    </body>
    

    You can see it in action at https://codepen.io/scheinercc/pen/KEowRK (old link: http://magnetiq.com/exports/toc.htm (Works in IE, FF, Safari, Opera))

    0 讨论(0)
  • 2020-12-12 18:11

    I've modified the function in AtesGoral's accepted answer to output correctly nested lists and valid HTML5.

    Just add the code below to your scripts and call TableOfContents(container, output); on load, where container is the class or id of your content element and output is the class or id of the TOC element. Default values are '#contents' and '#toc', respectively.

    See http://codepen.io/aufmkolk/pen/RWKLzr for a working demo.

    function TableOfContents(container, output) {
    var toc = "";
    var level = 0;
    var container = document.querySelector(container) || document.querySelector('#contents');
    var output = output || '#toc';
    
    container.innerHTML =
        container.innerHTML.replace(
            /<h([\d])>([^<]+)<\/h([\d])>/gi,
            function (str, openLevel, titleText, closeLevel) {
                if (openLevel != closeLevel) {
                    return str;
                }
    
                if (openLevel > level) {
                    toc += (new Array(openLevel - level + 1)).join('<ul>');
                } else if (openLevel < level) {
                    toc += (new Array(level - openLevel + 1)).join('</li></ul>');
                } else {
                    toc += (new Array(level+ 1)).join('</li>');
                }
    
                level = parseInt(openLevel);
    
                var anchor = titleText.replace(/ /g, "_");
                toc += '<li><a href="#' + anchor + '">' + titleText
                    + '</a>';
    
                return '<h' + openLevel + '><a href="#' + anchor + '" id="' + anchor + '">'
                    + titleText + '</a></h' + closeLevel + '>';
            }
        );
    
    if (level) {
        toc += (new Array(level + 1)).join('</ul>');
    }
    document.querySelector(output).innerHTML += toc;
    };
    
    0 讨论(0)
  • 2020-12-12 18:13

    So I had a problem with the answer provided by @Ates Goral and @Hendrik, I use a WYSIWYG which adds some html snippets in the H1-h6 elements like br tags and the rest. So the code breaks and doesn't recognize it as a valid h element since it doesn't match the search pattern. Also some WYSIWYG leave empty h-tags which are not excluded since they have no content. And the various modifications that follows often break with the same problem. A major bug I fixed was that if you had a heading, with the same text it would only reference the first- solutions provided by @Ates Goral and @Hendrik

    I should state that this solution is good if you are generating the table of content from data stored in the database. And I used some of the solutions above and built upon, especially @Ates Goral and @Hendrik

    function TableOfContents(container, output) {
            var txt = "toc-"; 
            var toc = "";
            var start = 0;
            var output = output || '#toc';
    
            var container = document.querySelector(container) || document.querySelector('#contents');
            var c = container.children;
    
            for (var i = 0; i < c.length; i++) {
            var isHeading = c[i].nodeName.match(/^H\d+$/) ;
            if(c[i].nodeName.match(/^H\d+$/)){
                var level = c[i].nodeName.substr(1);
    // get header content regardless of whether it contains a html or not that breaks the reg exp pattern
                var headerText = (c[i].textContent);
    // generate unique ids as tag anchors
                var anchor = txt+i;
    
                var tag = '<a href="#' + anchor + '" id="' + anchor + '">' + headerText + '</a>';
    
                c[i].innerHTML = tag;
    
                if(headerText){
                    if (level > start) {
                        toc += (new Array(level - start + 1)).join('<ul>');
                    } else if (level < start) {
                        toc += (new Array(start - level + 1)).join('</li></ul>');
                    } else {
                        toc += (new Array(start+ 1)).join('</li>');
                    }
                    start = parseInt(level);
                    toc += '<li><a href="#' + anchor + '">' + headerText + '</a>';
                }
            }
        }
        if (start) {
            toc += (new Array(start + 1)).join('</ul>');
        }
        document.querySelector(output).innerHTML += toc;
    }
    
    document.addEventListener('DOMContentLoaded', function() {
        TableOfContents();
      }
      ); 
    
    0 讨论(0)
  • 2020-12-12 18:14

    after page load, cycle through the DOM and look for the elements that interest you. build a nice list of anchors and add it to the document at the place of your desire.

    0 讨论(0)
  • 2020-12-12 18:19

    You can create dynamic table of contents for any HTML document using JavaScript which can show the list of headings from h1 to h6 with links to the headings and make easier to navigate through the document using the following steps.

    At first create window.onload function that runs automatically when the document finishes loading as given below

    window.onload=function(){
    
    function getSelectedText(){
    if (window.getSelection)
    return window.getSelection().toString()+"
    "+document.URL;
    else if (document.selection)
     return document.selection.createRange().text+"
    "+document.URL;
    }
    var toc=document.getElementById("TOC");
    if(!toc) {
     toc=document.createElement("div");
     toc.id="TOC";
     document.body.insertBefore(toc, document.body.firstChild);
    }

    Add the following codes to the function to find all through tags and sets them as headings.

    var headings;
    if (document.querySelectorAll) 
    headings=document.querySelectorAll("h1, h2, h3, h4, h5, h6");
    else
    headings=findHeadings(document.body, []);

    Use the following CSS code to design the Table of contents

    #TOC {border:solid black 1px; margin:10px; padding:10px;}
    .TOCEntry{font-family:sans-serief;}
    .TOCEntry a{text-decoration:none;}
    .TOCLevel1{font-size:17pt; font-weight:bold;}
    .TOCLevel2{font-size:16pt; font-weight:bold;}
    .TOCLevel3{font-size:15pt; font-weight:bold;}
    .TOCLevel4{font-size:14pt; margin-left:.25in;}
    .TOCSectNum{display:none;}
    

    Here is a full JavaScript Code To Create Table of Contents within script tag.

    window.onload=function(){
    
    function getSelectedText(){
    if (window.getSelection)
    return window.getSelection().toString()+"<br/>"+document.URL;
    else if (document.selection)
     return document.selection.createRange().text+"<br/>"+document.URL;
    }
    
    var toc=document.getElementById("TOC");
    if(!toc) {
     toc=document.createElement("div");
     toc.id="TOC";
     document.body.insertBefore(toc, document.body.firstChild);
    }
    var headings;
    if (document.querySelectorAll) 
    headings=document.querySelectorAll("h1, h2, h3, h4, h5, h6");
    else
    headings=findHeadings(document.body, []);
    
    function findHeadings(root, sects){
     for(var c=root.firstChild; c!=null; c=c.nextSibling){
    if (c.nodeType!==1) continue;
    if (c.tagName.length==2 && c.tagName.charAt(0)=="H")
    sects.push(c);
    else
    findHeadings(c, sects);
    }
    return sects;
    }
    
    var sectionNumbers=[0,0,0,0,0,0];
    
    for(var h=0; h<headings.length; h++) {
     var heading=headings[h];
    
    if(heading.parentNode==toc) continue;
    
    var level=parseInt(heading.tagName.charAt(1));
    if (isNaN(level)||level<1||level>6) continue;
    
    sectionNumbers[level-1]++;
    for(var i=level; i<6; i++) sectionNumbers[i]=0;
    
    var sectionNumber=sectionNumbers.slice(0, level).join(".");
    
    var span=document.createElement("span");
    span.className="TOCSectNum";
    span.innerHTML=sectionNumber;
    heading.insertBefore(span, heading.firstChild);
    heading.id="TOC"+sectionNumber;
    var anchor=document.createElement("a");
    heading.parentNode.insertBefore(anchor, heading);
    anchor.appendChild(heading);
    
    var link=document.createElement("a");
    link.href="#TOC"+sectionNumber; 
    link.innerHTML=heading.innerHTML;
    
    var entry=document.createElement("div");
    entry.className="TOCEntry TOCLevel" + level;
    entry.appendChild(link);
    
    toc.appendChild(entry);
    }
    };
    Source:How to Create Table of Contents Using JavaScript

    0 讨论(0)
提交回复
热议问题