Есть ли решение на JavaScript для создания «оглавления» страницы?

У меня есть заголовки в тегах от <h1> до <h6>. Есть ли способ, которым я могу использовать JavaScript для создания оглавления для содержимого, которое также служит в качестве якорных тегов?

Я бы хотел, чтобы результат был примерно таким:

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

В настоящее время я не использую платформу JavaScript, но не понимаю, почему я не могу ее использовать.

Я также ищу что-нибудь готовое, так как я предполагаю, что это обычная проблема, но если нет, было бы неплохо начать с того, чтобы свернуть свое собственное.

Не могли бы вы немного уточнить? Может быть, вам пригодится фрагмент HTML.

Mark Biek 09.10.2008 18:56

Это должно быть легко. Укажите желаемую структуру в HTML.

Marko Dumic 09.10.2008 18:56

Кроме того, можем ли мы предположить использование любого фреймворка Javascript, такого как Prototype или jQuery?

Mark Biek 09.10.2008 19:00

В вашем примере элементы <li> с отступом должны быть вложены в большее количество элементов <ol>.

Ates Goral 09.10.2008 20:24
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
29
4
19 988
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

после загрузки страницы пролистайте DOM и найдите интересующие вас элементы. составьте красивый список якорей и добавьте его в документ в желаемом месте.

Вы ищете готовое решение или спрашиваете, как это можно реализовать?

Для последнего вы можете использовать getElementsByTagName() рекурсивно на <h1> через <h6> XPath для перебора всех элементов <h*> и построения соответствующих вложенных списков <ul> или <ol>. Вам также нужно будет добавить теги <a> к заголовкам.

Использование Xpath предполагает, что документ будет иметь правильный формат XHTML.

Diodeus - James MacFarlane 09.10.2008 19:13

Да, и это также предполагает, что браузер поддерживает XPath. Думаю, не самое универсальное решение ...

Ates Goral 09.10.2008 19:21

На ум приходит JQuery как быстрое и простое решение. Быстрый поиск в Google по запросу оглавление jquery дает два многообещающих результата:

Итак, эти «решения» не воссоздают структуру рекурсивно (со всеми h1..h6), верно? -

Sergey Ilinsky 09.10.2008 19:29

Посмотрите на компонент, который вы ищете, на этой странице: Новое изобретение XMLHttpRequest: кроссбраузерная реализация с возможностями сниффинга

Он просматривает весь документ и создает оглавление со всеми элементами h1-h6, отраженными в открываемой (при наведении) структуре. Компонент является автономным и не использует никаких библиотек.

Ответ принят как подходящий

Я не мог устоять перед быстрой реализацией.

Добавьте следующий скрипт в любое место на вашей странице:

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

Не проверял, но выглядит солидно.

Thomas Owens 05.01.2009 16:06

Мне пришлось поместить скрипт в конец тега body, чтобы у DOM была возможность инициализировать перед запуском скрипта.

Nathan 15.12.2014 19:53

Ссылка больше не действительна.

Souin 18.02.2019 14:19

Спасибо за решение. Не могли бы вы дать несколько советов, как изменить js, чтобы покрыть вложенные заголовки, к сожалению, я не силен в JS

AlexPes 15.01.2020 10:19

Не работает, если для заголовков определены атрибуты, потому что регулярное выражение слишком простое.

Ini 18.03.2020 23:33

Если я использую /<h([\d])(?:[^<>]+)*>([^<]+)</h([\d])>/, то хром вылетает. Какие-либо предложения? Я думаю, это потому, что регулярное выражение жадное, но на первый взгляд я не вижу другого способа обойти *.

Ini 19.03.2020 11:32

Отлично работает в моем учебном курсе, созданном с помощью Markdown. Спасибо, что очистили мой утренний распорядок.

Jonathan Spiller 17.07.2020 12:04

Вот отличный сценарий для этого:

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. Вызовите функцию, например, в атрибуте onload вашего тела:

    <body onload = "htmlTableOfContents();"> 
    

Вот определение метода, который производит генерацию:

/**
 * 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
}

Есть опыт работы с этим? Судя по количеству ответов, могут быть причуды, и этот код выглядит подозрительно коротким.

Sridhar Sarnobat 29.10.2016 03:56

Этот код на удивление хорош. Здесь не упоминается возможность добавления таблицы стилей к элементам, чтобы они выглядели вложенными. Я немного улучшил его здесь для нотации html5 и сделал его еще короче!

Hasse Björk 11.12.2016 14:25

Вы можете создавать динамическое оглавление для любого HTML-документа с помощью JavaScript, который может отображать список заголовков от h1 до h6 со ссылками на заголовки и упростить навигацию по документу, используя следующие шаги.

Сначала создайте функцию window.onload, которая запускается автоматически, когда документ завершает загрузку, как указано ниже.

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

Добавьте в функцию следующие коды, чтобы найти все сквозные теги и установить их в качестве заголовков.

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

Используйте следующий код 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 для создания оглавления в теге скрипта.

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:Как создать оглавление с помощью JavaScript

Я изменил функцию в принятом ответе AtesGoral, чтобы выводить правильно вложенные списки и действительный HTML5.

Просто добавьте приведенный ниже код в свои сценарии и вызовите TableOfContents(container, output); при загрузке, где контейнер - это класс или идентификатор вашего элемента содержимого, а выход - это класс или идентификатор элемента TOC. Значения по умолчанию - "#contents" и "#toc" соответственно.

См. 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;
};

 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.info(list);

  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.info(toc);

Мне очень нравится ответ d13, но я хотел бы немного улучшить его, чтобы использовать нотации html5 и сохранить существующий идентификатор заголовка:

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.

В комментариях ниже Габриэль Хаутклок оптимизирует код статьи. Он также отмечает, что это не строго список, а структура div в виде списка, которая проще и генерирует более короткий код.

Похоже, что ответ, на который вы указали, был дан @ d13. Шридхар-Сарнобат был просто комментатором, который на самом деле не казался слишком довольным решением.

Fr0zenFyr 05.02.2019 08:24

Почему бы не использовать: var article = document.querySelector("article"); var headings = article ? [].slice.call(article.querySelectorAll( "h1, h2, h3, h4, h5, h6")) : [] (таким образом, избегая повторения селектора статей)? Тем не менее, он работает, но использует div, а не списки, как того требует вопрос.

Gabriel Hautclocq 31.01.2020 17:14

Итак, у меня возникла проблема с ответом, предоставленным @Ates Goral и @Hendrik, я использую WYSIWYG, который добавляет некоторые фрагменты html в элементы H1-h6, такие как теги 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();
  }
  ); 

Другие вопросы по теме