Автоматическое добавление якорей к заголовкам

17 ноября 2019 г. 0:00

Технически в html якорь для заголовков может выглядеть так:

<h2 id="my-title">Мой заголовок <a href="#my-title">¶</a></h2>
<p>Произвольный текст</p>

Результат выполнения такого кода:

Якорь html

Автоматически добавлять якорь к заголовкам можно как на стороне сервера, так и на стороне клиента. На стороне сервера придётся хранить дополнительный html-код, который будет загружаться из базы данных для отображения страницы, поэтому мне больше нравится посредством javascript добавлять якоря к заголовкам.

Постановка задачи

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

С технической стороны

При наведении на заголовок должен появляться знак ссылки на якорь, а при отведении мышки этот знак скрывать.

1. Добавление якоря к заголовкам транслитом

<h2>Мой заголовок</h2>
<p>Произвольный текст</p>

Должно преобразоваться в:

<h2 id="moi-zagolovok">Мой заголовок <a href="#moi-zagolovok">¶</a></h2>
<p>Произвольный текст</p>

Обратите внимание, что надпись Мой заголовок преобразуется в moi-zagolovok, чтобы при копировании и вставке ссылки страницы красиво отображалось, а не заменой кириллицы на код, иначе http://my-site/#мой-заголовок преобразился бы в http://my-site/#%D0%BC%D0%BE%D0%B9-%D0%B7%D0%B0%D0%B3%D0%BE%D0%BB%D0%BE%D0%B2%D0%BE%D0%BA (хотя, некоторые приложения преобразуют такие ссылки, например, если вставить в сообщение в ВКонтакте).

2. Добавление ссылки на якорь к заголовкам с id

<h2 id="my-title">Мой заголовок</h2>
<p>Произвольный текст</p>

Должно преобразоваться в:

<h2 id="my-title">Мой заголовок <a href="#my-title">¶</a></h2>
<p>Произвольный текст</p>

3. Добавление ссылки на якорь к заголовкам по указанному id, который не содержится в заголовках

<div class="anchor_wr" id="my-title">
    <img src="my_pic.jpg">Вспомогательный текст</img>
    <h2>Мой заголовок</h2>
    <p>Произвольный текст</p>
</div>

Должно преобразоваться в:

<div class="anchor_wr" id="my-title">
    <img src="my_pic.jpg">Вспомогательный текст</img>
    <h2>Мой заголовок <a href="#my-title">¶</a></h2>
    <p>Произвольный текст</p>
</div>

По классу anchor_wr можно определить id и сформировать ссылку для заголовка. Это полезно, если нам нужно для заголовка добавить ссылку на якорь, который находится в любом месте документа.

Зачастую в этом случае определяется один заголовок (напр., h2) внутри тега с классом .anchor. Если будет несколько заголовков, то к каждому заголовку будет добавлена одинаковая ссылка, что тоже может быть полезно.

4. Добавление якоря к заголовкам по указанному data-anchor

<div id="my-title">
    <p>Вспомогательный текст</p>
    <h2 data-anchor="my-title">Мой заголовок</h2>
    <p>Произвольный текст</p>
</div>

Должно преобразоваться в:

<div id="my-title">
    <p>Вспомогательный текст</p>
    <h2 data-anchor="my-title">Мой заголовок <a href="#my-title">¶</a></h2>
    <p>Произвольный текст</p>
</div>

Это полезно, если нам нужно добавить ссылку на якорь, id которого указывается в атрибуте data-anchor.

5. Не добавлять ссылку на якорь при использовании класса .no_anchor

<div class="no_anchor">
    <h2>Мой заголовок 1</h2>
    <p>Произвольный текст</p>
</div>

<h2 class="no_anchor">Мой заголовок 2</h2>

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

Со стороны адаптивности

Как правило для заголовков знак  ссылки на якорь располагают справа (реже слева). Недостаточно просто добавить знак в конец заголовка, так как может быть случай, когда заголовок в несколько слов как раз входит в ширину элемента, где он расположен, но знак при этом перенесётся на другую строку. Это будет выглядеть примерно так:

Знак ссылки на якорь неправильно отображается

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

<h2 id="moi-zagolovok">Мой очень длинный супер информативный <span style="white-space: nowrap;">заголовок <a href="#moi-zagolovok">¶</a></span></h2>
<p>Произвольный текст</p>

Теперь будет отображаться лучше:

Хорошее отображение якоря html

Но этого не достаточно. Если текст выравнивать по центру, то при наведении мышкой будет текст прыгать влево, то есть сам заголовок чуть смещаться влево из-за добавления знака, чтобы выравнить весь блок по центру.

Для решения данной проблемы можно для знака использовать свойство position: absolute;. Тогда при любом выравнивании заголовка при наведении мышкой будет появляться знак без каких-либо дёрганий текста.

Программный код

Ниже приведён полный пример кода, который используется на данном сайте vivazzi.pro с поддержкой перевода. Функция для добавления ссылок на якорь вынесена в отдельный файл anchorify.js. Также подключён вспомогательный файл transliterate.js, который требуется для транслита заголовков там, где id не указан.

test.html
<script src="jquery/jquery-2.2.0.min.js"></script>

<script src="anchorify/transliterate.js"></script>

<script>
    var anchorify_lang = {
        permalink: '{% trans 'Permalink to this headline' %}'  <!-- Используйте свой способ перевода слов. В Django используется {% trans 'Permalink to this headline' %} -->
    };
</script>
<script src="anchorify/anchorify.js"></script>
<link rel="stylesheet" href="anchorify/anchorify.css">


<!-- 1. Добавление якоря к заголовкам транслитом -->
<h2>Мой заголовок</h2>
<p>Произвольный текст</p>

<!-- 2. Добавление ссылки на якорь к заголовкам с id -->
<h2 id="my-title-2">Мой заголовок 2</h2>
<p>Произвольный текст</p>

<!-- 3. Добавление ссылки на якорь к заголовкам по указанному id, который не содержится в заголовках -->
<div class="anchor_wr" id="my-title-3">
    <img src="my_pic.jpg">Вспомогательный текст</img>
    <h2>Мой заголовок</h2>
    <p>Произвольный текст</p>
</div>

<!-- 4. Добавление якоря к заголовкам по указанному data-anchor -->
<div id="my-title-4">
    <p>Вспомогательный текст</p>
    <h2 data-anchor="my-title-4">Мой заголовок</h2>
    <p>Произвольный текст</p>
</div>

<!-- 5. Не добавлять ссылку на якорь при использовании класса .no_anchor -->
<div class="no_anchor">
    <h2>Мой заголовок 1</h2>
    <p>Произвольный текст</p>
</div>

<h2 class="no_anchor">Мой заголовок 2</h2>
transliterate.js
var ALPHABET_AND_DIGITS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

function transliterate(s, is_slug, valid_characters, use_dot) {

    function check_string(checked_string) {
        for(var i=0; i<checked_string.length; i++) {
            if (valid_characters.indexOf(checked_string[i]) == -1)
                return false
        }

        return true
    }

    if (is_slug == undefined) is_slug = false;
    if (valid_characters == undefined) valid_characters = null;
    if (use_dot == undefined) use_dot = false;

    if (!valid_characters){
        valid_characters = ALPHABET_AND_DIGITS + '- ';
        if (!is_slug) valid_characters += '\\/._ ';

        if (use_dot && !('.' in valid_characters)) valid_characters += '.'
    }

    var capital_letters = {
            'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'E', 'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'Y',
            'К': 'K', 'Л': 'L', 'М': 'M', 'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U', 'Ф': 'F',
            'Х': 'H', 'Ц': 'Ts', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sch', 'Ъ': '', 'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu',
            'Я': 'Ya', ' ': is_slug ? '-' : '_'
        },

        lower_case_letters = {
            'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'e', 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y',
            'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f',
            'х': 'h', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
        },

        res = '', char = '';

    s = s.toString();
    
    for(var i=0; i<s.length; i++) {
        char = s[i].replace(/\s/g, ' ');
        if (char in lower_case_letters) {
            char = lower_case_letters[char]
        } else if (char in capital_letters){
            char = capital_letters[char];
            if (s.length > i + 1) {
                if (!(s[i + 1] in lower_case_letters)) char = char.toUpperCase()
            }
            else char = char.toUpperCase()
        }

        if (check_string(char)) {
            if (is_slug) char = char.toLowerCase();
            res += char
        }
    }

    return res
}

Я написал более универсальную функцию transliterate, которая принимает параметры:

s - строка для преобразования
is_slug - если true, то пробелы и нижние подчёркивания будут преобразованы в знак дефиса "-" (как раз нам это пригодится для создания ссылки на якорь).
valid_characters - разрешённые символы, которые будут использованы для формирования ссылки
use_dot - если true, то добавляет знак точки "." к разрешённым символам
anchorify.js
$.each($('h2, h3, h4'), function () {

    if (!$(this).hasClass('no_anchor') && $(this).parents('.no_anchor').length === 0){

        if (!$(this).attr('id') && !$(this).data('anchor') && $(this).parents('.anchor_wr').length === 0) $(this).attr({'id': transliterate($(this)[0].innerText, true)});  // проверяем, нужно ли добавлять к заголовку id

        $(this).hover(function () {
            var $anchor = $(this).find('.anchor');

            // при первом наведении мышкой на заголовок ссылки пока нет, поэтому её нужно добавить
            if ($anchor.length === 0){
                var link = $(this).data('anchor') || $(this).attr('id');  // пытаемся получить либо id заголовка, либо значение атрибута data-anchor

                // если ссылка не определена, пытаемся найти родительский элемент с классом .anchor_wr
                if (!link) {
                    var $anchor_wr = $(this).parents('.anchor_wr');
                    if ($anchor_wr.length > 0) link = $anchor_wr.attr('id');
                }

                // если ссылка определена, можно добавить её в заголовок
                if (link) {
                    var parts = $(this).text().split(' ');  // возвращаем массив слов заголовка
                    if (parts) {
                        var last_word = parts.pop();  // присваиваем переменной последнее слово из массива слов, удаляя его из массива parts
                        $(this).html(parts.join(' ') + ' <span style="position: relative;">' + last_word + '</span>');  // оставшиеся элементы массива соединяем пробелом и добавляем последнее слово, обёрнутое тегом span
                        $(this).find('span').append('<a class="anchor" href="#'+ link +'" title="'+anchorify_lang.permalink+'">¶</a>');  // добавляем ссылку в тег span
                    }
                }
            } else {  // при повторном наведении просто отображаем ссылку
                $anchor.css({'display': 'inline-block'});
            }
        }, function () {
            // После отведения мышки от заголовка ссылку просто скрываем (а не удаляем, чтобы заново её не создавать)
            var $anchor = $(this).find('.anchor');
            if ($anchor.length > 0) $anchor.css({'display': 'none'});
        });
    }
});

Если вы захотите изменить список тегов или добавить свои теги, для которых нужно добавлять ссылку на якорь, то в первой строке кода вместо $('h2, h3, h4') можно использовать, например, $('h2, h3, h4, h5, h6, my_tag').

anchorify.css
.anchor {text-decoration: none;padding-left: 12px;font-size: .75em;position: absolute;line-height: initial;}

@media (max-width: 767px) {
    .anchor {padding-left: 3px;}
}

На больших экранах, как правило, справа отступ более 15px, поэтому сам знак можно размещать на расстоянии 12px от последнего слова. А при малой ширине (напр., менее 768px) экрана отступ от края 5px, поэтому и знак должен располагаться ближе, чтобы не вылезти за пределы ширины блока, в котором находится заголовок. Измените свойства стиля в соответствии с вашим дизайном сайта.

Теперь к вашим заголовкам будут автоматически добавляться ссылки на якорь!

Мой код не претендует на оптимальный. Например, в anchorify.js можно было обойтись без Jquery. Если вы доработаете и предложите свой более оптимизированный вариант кода, то буду рад!

Оцените статью

0 из 5 (всего 0 оценок)

Поля, отмеченные звёздочкой ( * ) , являются обязательными.

Спасибо за ваш отзыв!

После нажатия кнопки "Отправить" ваше сообщение будет доставлено мне на почту.

Автор статьи

Права на использование материала, расположенного на этой странице https://vivazzi.pro/it/anchor/:

Разрешается копировать материал с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Мальцев Артём
Ссылка на статью: <a href="https://vivazzi.pro/it/anchor/">https://vivazzi.pro/it/anchor/</a>

Подробнее: Правила использования сайта

Комментариев: 0

Вы можете оставить комментарий как незарегистрированный пользователь.

Но зарегистрировавшись, вы сможете:

  • получать оповещения об ответах
  • просматривать свои комментарии
  • иметь возможность использовать все функции разработанных сервисов

Для комментрирования от своего имени, войдите или зарегистрируйтесь обычным способом или через социальные сети:

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте