У меня есть заголовки в тегах от <h1> до <h6>. Есть ли способ, которым я могу использовать JavaScript для создания оглавления для содержимого, которое также служит в качестве якорных тегов?
Я бы хотел, чтобы результат был примерно таким:
<ol>
<li>Header 1</li>
<li>Header 1</li>
<li>Header 2</li>
<li>Header 3</li>
</ol>
В настоящее время я не использую платформу JavaScript, но не понимаю, почему я не могу ее использовать.
Я также ищу что-нибудь готовое, так как я предполагаю, что это обычная проблема, но если нет, было бы неплохо начать с того, чтобы свернуть свое собственное.
Это должно быть легко. Укажите желаемую структуру в HTML.
Кроме того, можем ли мы предположить использование любого фреймворка Javascript, такого как Prototype или jQuery?
В вашем примере элементы <li> с отступом должны быть вложены в большее количество элементов <ol>.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


после загрузки страницы пролистайте DOM и найдите интересующие вас элементы. составьте красивый список якорей и добавьте его в документ в желаемом месте.
Вы ищете готовое решение или спрашиваете, как это можно реализовать?
Для последнего вы можете использовать XPath для перебора всех элементов getElementsByTagName() рекурсивно на <h1> через <h6><h*> и построения соответствующих вложенных списков <ul> или <ol>. Вам также нужно будет добавить теги <a> к заголовкам.
Использование Xpath предполагает, что документ будет иметь правильный формат XHTML.
Да, и это также предполагает, что браузер поддерживает XPath. Думаю, не самое универсальное решение ...
На ум приходит JQuery как быстрое и простое решение. Быстрый поиск в Google по запросу оглавление jquery дает два многообещающих результата:
Итак, эти «решения» не воссоздают структуру рекурсивно (со всеми h1..h6), верно? -
Посмотрите на компонент, который вы ищете, на этой странице: Новое изобретение 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))
Не проверял, но выглядит солидно.
Мне пришлось поместить скрипт в конец тега body, чтобы у DOM была возможность инициализировать перед запуском скрипта.
Ссылка больше не действительна.
Спасибо за решение. Не могли бы вы дать несколько советов, как изменить js, чтобы покрыть вложенные заголовки, к сожалению, я не силен в JS
Не работает, если для заголовков определены атрибуты, потому что регулярное выражение слишком простое.
Если я использую /<h([\d])(?:[^<>]+)*>([^<]+)</h([\d])>/, то хром вылетает. Какие-либо предложения? Я думаю, это потому, что регулярное выражение жадное, но на первый взгляд я не вижу другого способа обойти *.
Отлично работает в моем учебном курсе, созданном с помощью Markdown. Спасибо, что очистили мой утренний распорядок.
Вот отличный сценарий для этого:
https://github.com/matthewkastor/html-table-of-contents/wiki
Чтобы использовать это:
Добавьте этот тег:
<script src = "./node_modules/html-table-of-contents/src/html-table-of-contents.js" type = "text/javascript">
Вызовите функцию, например, в атрибуте 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
}
Есть опыт работы с этим? Судя по количеству ответов, могут быть причуды, и этот код выглядит подозрительно коротким.
Этот код на удивление хорош. Здесь не упоминается возможность добавления таблицы стилей к элементам, чтобы они выглядели вложенными. Я немного улучшил его здесь для нотации html5 и сделал его еще короче!
Вы можете создавать динамическое оглавление для любого 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);
}
};Я изменил функцию в принятом ответе 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. Шридхар-Сарнобат был просто комментатором, который на самом деле не казался слишком довольным решением.
Почему бы не использовать: var article = document.querySelector("article"); var headings = article ? [].slice.call(article.querySelectorAll( "h1, h2, h3, h4, h5, h6")) : [] (таким образом, избегая повторения селектора статей)? Тем не менее, он работает, но использует div, а не списки, как того требует вопрос.
Итак, у меня возникла проблема с ответом, предоставленным @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();
}
);
Не могли бы вы немного уточнить? Может быть, вам пригодится фрагмент HTML.