是否有一个JavaScript解决方案来生成页面的“目录”?

时间:2008-10-09 14:54:32

标签: javascript

我在<h1><h6>标记中有标题。有没有办法可以使用JavaScript来生成用作锚标签的内容的目录?

我希望输出类似于:

<ol>
    <li>Header 1</li>
    <li>Header 1</li>
        <li>Header 2</li>
            <li>Header 3</li>
</ol>

我目前没有使用JavaScript框架,但我不明白为什么我不能使用它。

我也在寻找已完成的事情,因为我猜这是一个常见的问题,但如果没有,那么我自己的起点就会很好。

14 个答案:

答案 0 :(得分:33)

我无法抗拒快速实施。

在页面的任何位置添加以下脚本:

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;
};

您的网页应该是这样的结构:

<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>

你可以在https://codepen.io/scheinercc/pen/KEowRK(旧链接:http://magnetiq.com/exports/toc.htm(在IE,FF,Safari,Opera中工作)中看到它的实际效果。)

答案 1 :(得分:6)

JQuery是一种快速简便的解决方案。快速谷歌搜索 jquery目录会产生两个有希望的结果:

答案 2 :(得分:4)

这是一个很棒的脚本:

https://github.com/matthewkastor/html-table-of-contents/wiki

使用它:

  1. 添加此标记:

    <script src="./node_modules/html-table-of-contents/src/html-table-of-contents.js" type="text/javascript">
    
  2. 调用该函数,例如在您的body的onload属性中:

    <body onload="htmlTableOfContents();"> 
    
  3. 以下是生成代码的方法的定义:

    /**
     * Generates a table of contents for your document based on the headings
     *  present. Anchors are injected into the document and the
     *  entries in the table of contents are linked to them. The table of
     *  contents will be generated inside of the first element with the id `toc`.
     * @param {HTMLDOMDocument} documentRef Optional A reference to the document
     *  object. Defaults to `document`.
     * @author Matthew Christopher Kastor-Inare III
     * @version 20130726
     * @example
     * // call this after the page has loaded
     * htmlTableOfContents();
     */
    function htmlTableOfContents (documentRef) {
        var documentRef = documentRef || document;
        var toc = documentRef.getElementById('toc');
        var headings = [].slice.call(documentRef.body.querySelectorAll('h1, h2, h3, h4, h5, h6'));
        headings.forEach(function (heading, index) {
            var anchor = documentRef.createElement('a');
            anchor.setAttribute('name', 'toc' + index);
            anchor.setAttribute('id', 'toc' + index);
    
            var link = documentRef.createElement('a');
            link.setAttribute('href', '#toc' + index);
            link.textContent = heading.textContent;
    
            var div = documentRef.createElement('div');
            div.setAttribute('class', heading.tagName.toLowerCase());
    
            div.appendChild(link);
            toc.appendChild(div);
            heading.parentNode.insertBefore(anchor, heading);
        });
    }
    
    try {
         module.exports = htmlTableOfContents;
    } catch (e) {
        // module.exports is not defined
    }
    

答案 3 :(得分:3)

我已经修改了AtesGoral接受的答案中的功能,以正确输出嵌套列表和有效的HTML5。

只需将以下代码添加到您的脚本中,然后在加载时调用TableOfContents(container, output);,其中容器是您的内容元素的类或ID,输出是TOC元素的类或id。默认值为&#39; #content&#39;和&#39;#toc&#39;分别。

有关正常工作的演示,请参阅http://codepen.io/aufmkolk/pen/RWKLzr

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;
};

答案 4 :(得分:2)

我真的很喜欢Sridhar-Sarnobat's answer,但我想稍微改进它以使用html5表示法并保留标题的现有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
}

您可以通过在标题中包含脚本来使用它。

非常棒的是,您可以使用样式表作为目录:

<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>

在我的个人脚本中,我使用了略有不同的选择器:

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

主页面保存在<article></article>内,脚本将仅搜索主文章中的标题。我可以在目录中使用标题,例如<nav id="toc"><h3>Table of contents</h3></nav>或页脚/标题,而不会显示在toc内。

答案 5 :(得分:2)

所以我对@Ates Goral和@Hendrik提供的答案有疑问,我使用WYSIWYG,它在H1-h6元素中添加了一些html代码片段,例如br标签和其余标签。因此,代码将中断,并且由于与搜索模式不匹配而无法将其识别为有效的h元素。另外,某些WYSIWYG留下了空的H标签,由于它们没有内容,因此不排除在外。并且随后进行的各种修改经常会遇到相同的问题。 我修复的一个主要错误是,如果标题相同,则仅引用@Ates Goral和@Hendrik提供的第一个解决方案

我应该指出,如果您是根据数据库中存储的数据生成目录的,那么此解决方案是不错的选择。而且我使用了上面构建的一些解决方案,尤其是@Ates Goral和@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();
  }
  ); 

答案 6 :(得分:1)

您是在寻找预先打包的解决方案,还是在询问如何实施?

对于后者,您可以在getElementsByTagName()<h1> XPath上递归使用 <h6>迭代所有<h*>元素并构造相应的嵌套的<ul><ol>列表。您还必须将<a>标记添加到标题中。

答案 7 :(得分:1)

这是我为工作想出的目录的普通 JavaScript 版本。

它查找指定内容容器中的所有标题并为它们创建 ID,然后生成 TOC 链接。我让它添加样式类并使用嵌套列表来创建层次结构。

window.addEventListener('DOMContentLoaded', function (event) { // Let the DOM content load before running the script.
//Get all headings only from the actual contents.
var contentContainer = document.getElementById('content'); // Add this div to the html
var headings = contentContainer.querySelectorAll('h1,h2,h3,h4'); // You can do as many or as few headings as you need.

var tocContainer = document.getElementById('toc'); // Add this div to the HTML
// create ul element and set the attributes.
var ul = document.createElement('ul');

ul.setAttribute('id', 'tocList');
ul.setAttribute('class', 'sidenav')

// Loop through the headings NodeList
for (i = 0; i <= headings.length - 1; i++) {

    var id = headings[i].innerHTML.toLowerCase().replace(/ /g, "-"); // Set the ID to the header text, all lower case with hyphens instead of spaces.
    var level = headings[i].localName.replace("h", ""); // Getting the header a level for hierarchy
    var title = headings[i].innerHTML; // Set the title to the text of the header

    headings[i].setAttribute("id", id)  // Set header ID to its text in lower case text with hyphens instead of spaces.

    var li = document.createElement('li');     // create li element.
    li.setAttribute('class', 'sidenav__item') // Assign a class to the li

    var a = document.createElement('a'); // Create a link
    a.setAttribute("href", "#" + id) // Set the href to the heading ID
    a.innerHTML = title; // Set the link text to the heading text
    
    // Create the hierarchy
    if(level == 1) {
        li.appendChild(a); // Append the link to the list item
        ul.appendChild(li);     // append li to ul.
    } else if (level == 2) {
        child = document.createElement('ul'); // Create a sub-list
        child.setAttribute('class', 'sidenav__sublist')
        li.appendChild(a); 
        child.appendChild(li);
        ul.appendChild(child);
    } else if (level == 3) {
        grandchild = document.createElement('ul');
        grandchild.setAttribute('class', 'sidenav__sublist')
        li.appendChild(a);
        grandchild.appendChild(li);
        child.appendChild(grandchild);
    } else if (level == 4) {
        great_grandchild = document.createElement('ul');
        great_grandchild.setAttribute('class', 'sidenav__sublist');
        li.append(a);
        great_grandchild.appendChild(li);
        grandchild.appendChild(great_grandchild);
    }
}

toc.appendChild(ul);       // add list to the container

// Add a class to the first list item to allow for toggling active state.
var links = tocContainer.getElementsByClassName("sidenav__item");

links[0].classList.add('current');

// Loop through the links and add the active class to the current/clicked link
for (var i = 0; i < links.length; i++) {
    links[i].addEventListener("click", function() {
        var current = document.getElementsByClassName("current");
        current[0].className = current[0].className.replace(" current", "");
        this.className += " current";
    });
}
});

我在 CodePen 有一个演示。

答案 8 :(得分:0)

页面加载后,循环访问DOM并查找您感兴趣的元素。建立一个很好的锚点列表,并将其添加到您想要的位置的文档中。

答案 9 :(得分:0)

在此页面上查看您要查找的组件:Re-inventing XMLHttpRequest: Cross-browser implementation with sniffing capabilities

它遍历整个文档并创建一个TOC,其中所有h1-h6元素都在可打开(悬停)结构中反映出来。该组件是独立的,不使用任何库。

答案 10 :(得分:0)

您可以create dynamic table of contents for any HTML document using JavaScript显示从h1到h6的标题列表,其中包含指向标题的链接,并使用以下步骤更轻松地浏览文档。

首先创建window.onload函数,该函数在文档完成加载时自动运行,如下所示

&#13;
&#13;
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);
}
&#13;
&#13;
&#13;

将以下代码添加到函数中以查找所有直通标记并将其设置为标题。

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

使用以下CSS代码设计目录

#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;}

以下是在脚本标记内创建目录的完整JavaScript代码。

&#13;
&#13;
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);
}
};
&#13;
&#13;
&#13; 来源:How to Create Table of Contents Using JavaScript

答案 11 :(得分:0)

 let headers = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
  let list    = document.createElement('ol');

  let _el = list;
  for(i=0; i<headers.length; i++) {
    while(_el) {
      let li = document.createElement('li');
      li.innerText = headers[i].innerText;
      li.setAttribute('tagName', headers[i].tagName);
      if(_el.getAttribute('tagName') < headers[i].tagName) {
        let ol = _el.children.length > 0 ? ol = _el.querySelector('ol') : document.createElement('ol');
        ol.appendChild(li);
        _el.appendChild(ol);
        _el = li;
        break;
      } else {
        if(_el.tagName == 'OL') {
         _el.appendChild(li);
         _el = li;
         break;
        } else if (!_el.parentNode.parentNode) {
          _el.parentNode.appendChild(li);
          _el = li;
          break;
        }
        else {
          _el = _el.parentNode.parentNode;
        }
      }
    }
  }
  console.log(list);

答案 12 :(得分:0)

  this.insert = (el, h) => {
    let li = document.createElement('li');
    li.innerText = h.innerText;
    li.setAttribute('tagName', h.tagName);
    if(el.tagName == 'OL') {
      el.appendChild(li);
      return li;
    } else if(el.getAttribute('tagName') < h.tagName) {
      let ol = el.children.length > 0 ? ol = el.querySelector('ol') : document.createElement('ol');
      ol.appendChild(li);
      el.appendChild(ol);
      return li;
    } else if(!el.parentNode.parentNode) {
      el.parentNode.appendChild(li);
      return li;
    } else {
      return this.insert(el.parentNode.parentNode, h);
    }
  }

  this.parse = (headers) => {
    let list = document.createElement('ol');
    let el = list;
    for(i=0; i<headers.length; i++) {
      el = this.insert(el, headers[i]);
    }
    return list;
  }
  let headers = document.querySelectorAll('h1,h2,h3,h4,h5,h6');
  let toc = this.parse(headers);
  console.log(toc);

答案 13 :(得分:0)

这是一个基于 jQuery 的函数,它分析提供的内容中的标题元素 <h1>, <h2> ... 并返回带有 TOC 层次结构的 jQuery 对象,您可以将其附加到您的页面:

<div class="table_of_contents">
    <ul>
        <a href="#Level1_Heading">Level1 Heading</a>
        <li>
            <a href="#Level2_Heading">Level2 Heading</a>
            <ul>
                ...
        </li>
    </ul>
</div>

它还通过插入不可见的 $content 锚点来修改 <a name="...">,用户点击创建的目录项时会跳转到这些锚点。

function generate_toc($content) {
    let $toc = $("<div>", {class: "table_of_contents"});
    let level2$toc_item = {0: $toc};
    let used_anchors = {};
    $content.find("h1, h2, h3, h4, h5").each(function() {
        // find out the level of heading
        let level = parseInt($(this).prop("tagName").replace(/[^0-9]/gi, ""));
        let heading_text = $(this).text();
        // define the unique anchor id
        let heading_anchor = heading_text.replace(/[^a-z0-9]/gi, "_");
        while (heading_anchor in used_anchors) {heading_anchor += "_";}
        used_anchors[heading_anchor] = true;                
        // add target point into main content
        $(this).prepend($("<a>", {name: heading_anchor}));
        // find the parent level for TOC item
        let parent_level = level-1;
        for (; !(parent_level in level2$toc_item); parent_level--); 
        // remove all jumped over levels
        for (let l in level2$toc_item) {
            if (parseInt(l) > parent_level) {
                delete level2$toc_item[l];
            }
        }
        let $parent = level2$toc_item[parent_level];
        // create new TOC item inside parent's <ul>
        level2$toc_item[level] = $("<li>").appendTo(
            $parent.children("ul").length == 1
            ? $($parent.children("ul")[0])
            : $("<ul>").appendTo($parent)
        ).append($("<a>", {href: `#${heading_anchor}`}).text(heading_text));
    });
    return $toc;
}

使用示例:

$("body").prepend(generate_toc("body"));