Создание оглавления статей на сайте с помощью JavaScript

Создание оглавления статей на сайте с помощью JavaScript
Содержание:
  1. Создание оглавления из заголовков h2
  2. Подсветка пунктов оглавления по мере прокрутки страницы
  3. Скрипт для создания многоуровневого оглавления
  4. Многоуровневое меню с выделением активных пунктов
  5. Комментарии

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

Создание оглавления из заголовков h2

Процесс написания скрипта для формирования оглавления из тегов <h2> представим в виде следующих шагов:

1. Создадим HTML обёртку для оглавления и присвоим её переменной tpl:

JavaScript
const tpl = '<section class="table-of-contents"><div style="font-weight: bold;">Содержание:</div><ul>{{contents}}</ul></section>';

Плейсхолдер {{contents}} впоследствии заменим на HTML код оглавления.

2. Объявим переменную contents с помощью ключевого слова let и присвоим ей в качестве значения пустую строку:

JavaScript
let contents = '';

В эту переменную мы будем собирать HTML код оглавления.

3. Создадим переменную elHeaders и поместим в неё все найденные теги <h2> в элементе с классом article:

JavaScript
const elHeaders = document.querySelectorAll('.article h2');

Для выбора элементов используется метод querySelectorAll().

4. Сформируем HTML код оглавления на основе <h2>:

JavaScript
elHeaders.forEach((el, index) => {
  if (!el.id) {
    el.id = `id-${index}`;
  }
  const url = `${location.href.split('#')[0]}#${el.id}`;
  contents += `<li><a href="${url}">${el.textContent}</a></li>`;
});

В этом коде для перебора выбранных <h2> используется метод forEach(). Внутри forEach() мы сначала устанавливаем заголовку <h2> значение атрибута id, если у него его конечно нет. После этого формируем URL и сохраняем его в переменную url. Затем добавляем к значению переменной contents элемент оглавления.

5. Вставим HTML содержимое оглавления внутрь <aside> перед первым элементом с помощью метода insertAdjacentHTML:

JavaScript
document.querySelector('aside').insertAdjacentHTML('afterbegin', tpl.replace('{{contents}}', contents));

Конечный HTML получим взяв значение переменной tpl, в которой {{contents}} заменим на содержимое переменной contents.

В итоге мы получим следующий код на чистом JavaScript для автоматической генерации содержания статей:

JavaScript
const tpl = '<section class="table-of-contents"><div style="font-weight: bold;">Содержание:</div><ul>{{contents}}</ul></section>';
let contents = '';
const elHeaders = document.querySelectorAll('.article h2');
elHeaders.forEach((el, index) => {
  if (!el.id) {
    el.id = `id-${index}`;
  }
  const url = `${location.href.split('#')[0]}#${el.id}`;
  contents += `<li><a href="${url}">${el.textContent}</a></li>`;
});
document.querySelector('aside').insertAdjacentHTML('afterbegin', tpl.replace('{{contents}}', contents));
Создание оглавления для статей сайта на основе заголовков h2 на чистом JavaScript

Подсветка пунктов оглавления по мере прокрутки страницы

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

Отметку активного пункта в оглавлении будем выполнять посредством добавления класса active.

Код на чистом JavaScript:

JavaScript
window.addEventListener('scroll', () => {
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const elHeaders = document.querySelectorAll('h2, h3, h4');
  let headerId = '';
  for (let i = elHeaders.length - 1; i >= 0; i--) {
    if (elHeaders[i].getBoundingClientRect().top + window.pageYOffset - 200 < scrollTop) {
      headerId = elHeaders[i].id;
      break;
    }
  }
  document.querySelectorAll('.table-of-contents li.active').forEach(el => {
    el.classList.remove('active');
  });
  if (headerId) {
    document.querySelector(`a[href$="#${headerId}"]`).parentElement.classList.add('active');
  }
});
Создание оглавления для статей сайта на основе заголовков h2 с подсветкой пунктов на чистом JavaScript

В этом коде определение активного пункта по мере прокрутки страницы основано на обработке события scroll.

В обработчике мы сначала получаем число пикселей, на которые пользователь прокрутил текущий документ и сохраняем это значение в переменную scrollTop. На следующей строке получаем все заголовки <h2>, расположенные в элементе с классом article.

JavaScript
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const elHeaders = document.querySelectorAll('h2, h3, h4');

После этого объявляется переменная headerId и присваивается ей пустая строка. В эту переменную будет сохраняться значение атрибута id тега <h2>, который в данный момент изучает пользователь:

JavaScript
let headerId = '';

Далее с помощью цикла for перебираются заголовки и высчитывается среди них тот в котором сейчас находится пользователь. Значение id найденного <h2> сохраняется в переменную headerId:

JavaScript
for (let i = elHeaders.length - 1; i >= 0; i--) {
  if (elHeaders[i].getBoundingClientRect().top + window.pageYOffset - 200 < scrollTop) {
    headerId = elHeaders[i].id;
    break;
  }
}

Затем у элементов с классом active удаляется этот класс:

JavaScript
document.querySelectorAll('.table-of-contents li.active').forEach(el => {
  el.classList.remove('active');
});

Завершаем код установкой класса active элементу <li>. Для этого выбирается <a> с атрибутом href значение которого заканчивается на #${headerId}. После этого получаем родительский элемент и устанавливаем ему класс active:

JavaScript
document.querySelector(`a[href$="#${headerId}"]`).parentElement.classList.add('active');

Скрипт для создания многоуровневого оглавления

Изменим скрипт, приведённый выше, для генерирования многоуровневого меню на основе тегов <h2>, <h3> и <h4>:

JavaScript
const headers = [];
const indexes = [0];
// функция для получения предыдущего header
const getPrevHeader = (diff = 0) => {
  if ((indexes.length - diff) === 0) {
    return null;
  }
  let header = headers[indexes[0]];
  for (let i = 1, length = indexes.length - diff; i < length; i++) {
    header = header.contains[indexes[i]];
  }
  return header;
}
// функция для добавления item в headers
const addItemToHeaders = (el, diff) => {
  let header = headers;
  if (diff === 0) {
    header = indexes.length > 1 ? getPrevHeader(1).contains : header;
    indexes.length > 1 ? indexes[indexes.length - 1]++ : indexes[0]++;
  } else if (diff > 0) {
    header = getPrevHeader().contains;
    indexes.push(0);
  } else if (diff < 0) {
    const parentHeader = getPrevHeader(Math.abs(diff) + 1);
    for (let i = 0; i < Math.abs(diff); i++) {
      indexes.pop();
    }
    header = parentHeader ? parentHeader.contains : header;
    parentHeader ? indexes[indexes.length - 1]++ : indexes[0]++;
  }
  header.push({ el, contains: [] });
}
// добавим заголовки в headers
document.querySelectorAll('h2, h3, h4').forEach((el, index) => {
  if (!el.id) {
    el.id = `id-${index}`;
  }
  if (!index) {
    addItemToHeaders(el);
    return;
  }
  const diff = el.tagName.substring(1) - getPrevHeader().el.tagName.substring(1);
  addItemToHeaders(el, diff);
});
// сформируем оглавление страницы для вставки его на страницу
let html = '';
const createTableOfContents = (items) => {
  html += '<ol>';
  for (let i = 0, length = items.length; i < length; i++) {
    const url = `${location.href.split('#')[0]}#${items[i].el.id}`;
    html += `<li><a href="${url}">${items[i].el.textContent}</a>`;
    if (items[i].contains.length) {
      createTableOfContents(items[i].contains);
    }
    html += '</li>';
  }
  html += '</ol>';
}
createTableOfContents(headers);
html = `<section class="table-of-contents"><div style="font-weight: bold;">Содержание:</div>${html}</section>`;
// вставим оглавление в тег <aside>
document.querySelector('aside').insertAdjacentHTML('afterbegin', html);
Создание многоуровневого оглавления для статей сайта на чистом JavaScript

Многоуровневое меню с выделением активных пунктов

Для выделения оглавления при нахождении пользователя в соответствующем разделе необходимо в скрипт добавить ещё следующий код:

JavaScript
window.addEventListener('scroll', () => {
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const elHeaders = document.querySelectorAll('h2, h3, h4');
  let headerId = '';
  for (let i = elHeaders.length - 1; i >= 0; i--) {
    if (elHeaders[i].getBoundingClientRect().top + window.pageYOffset - 200 < scrollTop) {
      headerId = elHeaders[i].id;
      break;
    }
  }
  document.querySelectorAll('.table-of-contents a.active').forEach(el => {
    el.classList.remove('active');
  });
  if (headerId) {
    document.querySelector(`a[href$="#${headerId}"]`).classList.add('active');
  }
});
Создание многоуровневого оглавления для статей сайта на основе заголовков h2 с подсветкой пунктов на чистом JavaScript

Комментарии: 7

ElenaOzer
ElenaOzer
Александр, спасибо за статью. Подскажите, как из текущего оглавления как в примере, можно сделать многоуровневое оглавление? если у нас есть допустим H2 с вложениями H3 и т.д.
ElenaOzer
ElenaOzer
Чтобы они не просто шли подряд друг за другом, а появлялась вложенность
Содержание:
Раздел 1
	Раздел 1.1
	Раздел 1.2
Раздел 2
	Раздел 2.1
	Раздел 2.2
Раздел 3
	Раздел 3.1
	Раздел 3.2
Раздел 4
Раздел 5
Александр Мальцев
Александр Мальцев
Пожалуйста! Добавил для формирования многоуровневого оглавления код JavaScript в статью.
Серый
Серый
Еще хорошо было бы, что б оно не выводилось, в случае, если в тексте отсутствуют h2
Александр Мальцев
Александр Мальцев
Как это сделать можно посмотреть в этом примере.
Серый
Серый
Вот такой скрипт мягкой прокрутки у меня сработал здесь
<script>
    $("body").on('click', '[href*="#"]', function(e){
      var fixed_offset = 200;
      $('html,body').stop().animate({ scrollTop: $(this.hash).offset().top - fixed_offset }, 1000);
      e.preventDefault();
    });
</script>
Но хорошо бы доработать штуку так, что бы в нем уже был и мягкий скролл и может там подсветка какая-то пунктов прикрепляемого меню)))
Александр Мальцев
Александр Мальцев
Спасибо за код.