Создаем фиксированное меню на CSS, jQuery + решение проблемы с якорями
С момента публикации первой версии данной заметки прошло уже несколько лет, но текущая информация все еще актуальна. Просто хочу дополнить ее разбором одной нетипичной и сложной ситуации, с которой на днях столкнулся по фрилансу. Новичкам озвученные здесь приемы точно пригодятся, плюс запишу их для себя, т.к. похожие задачи возникают время от времени.
В статье я не буду вдаваться в подробности всех деталей кода, укажу лишь основные нюансы. Можете скопировать эти примеры и попрактиковаться с ними на своих проектах, ну а полным новичкам — не помешает дополнительно глянуть сервис обучения верстке Interneting is Hard.
В посте представлены такие вопросы:
- Как фиксировать меню при прокрутке страницы (это когда блок навигации «прижимается» к верху экрана). Рассмотрим:
- Прокрутка до ссылки якоря с фиксированным меню — без дополнительных хаков в этом случае не обойтись.
- Мобильное фиксированное меню + ссылки якоря (оригинальная задачка с универсальным решением).
- Плагин Page scroll to id — неплохое меню с якорными ссылками для WordPress.
Фиксированное меню на CSS + HTML
В общем случае при создании простого горизонтального фиксированного меню для сайта вам нужно использовать CSS свойства position:fixed и top:0. Также основному блоку контента придется задать верхний отступ margin-top.
Итак, возьмем простую структуру HTML страницы:
<div class="menu"> <a href="#main">Главная</a> <a href="#about">О нас</a> <a href="#contacts">Контакты</a> </div> <div class="main"> <p>Какой-то текст для примера.</p> </div> |
В файл стилей CSS добавляете:
.menu { overflow: hidden; background-color: #999; position: fixed; top: 0; width: 100%; } .menu a { float: left; display: block; color: #000; padding: 15px 15px; text-decoration: none; } .content { font-style: italic; width: 40%; margin-top: 60px; margin-left: 10px; } |
В результате получится такая картинка:
Ничего оригинального, но главное, что работает. Дабы увидеть эффект фиксированного меню при прокрутке страницы вам нужно будет добавить больше текста в блок контента (чтобы появилась полоса прокрутки).
Основные детали кода выше, как я уже говорил, это position:fixed, top:0 и margin-top: 60px. Верхний отступ может быть другим, если высота меню у вас меньше/больше.
Кстати, если требуется зафиксировать меню внизу страницы, заменяете код на:
. menu { position: fixed; bottom: 0; width: 100%; } |
Все то же самое, только вместо top указывается bottom:0. Единственное, нужно будет погуглить как правильно сделать отступ контенту снизу чтобы он тот скрывался за плашкой меню. Решение с margin-bottom:30px; почему-то не сработало.
Фиксированное меню с jQuery и CSS
Второй пример чуть более сложный — с применением библиотеки jQuery. Она встречается на многих сайтах, плюс в большинстве шаблонов WordPress и других CMS подключена изначально. Из Javascript функций здесь используется всего 2, поэтому данное решение можно считать достаточно легким по сравнению с некоторыми другими из интернета. Вот что в итоге должно получиться:
Алгоритм внедрения данного фиксированного меню с jQuery состоит из трех шагов. Первым делом добавляем HTML код:
<div class="nav-container"> <div class="nav"> <ul> <li><a href="">Home</a></li> <li><a href="">CSS</a></li> <li><a href="">PHP</a></li> <li><a href="">SEO</a></li> <li><a href="">jQuery</a></li> <li><a href="">Wordpress</a></li> <li><a href="">Services</a></li> </ul> <div class="clear"></div> /*clear floating div*/ </div> </div> |
Если вы хотите внедрить данный метод на уже готовое собственное меню, тут 2 пути: либо в CSS и JS ниже подставляете свои стили, либо подгоняете имеющийся HTML под вариант сверху.
В стилях размещаете следующие строки:
.nav-container{ background: url('images/nav_bg.jpg') repeat-x 0 0;} .f-nav{ z-index: 9999; position: fixed; left: 0; top: 0; width: 100%;} .nav { height: 42px;} .nav ul { list-style: none; } .nav ul li{float: left; margin-top: 6px; padding: 6px; border-right: 1px solid #ACACAC;} .nav ul li:first-child{ padding-left: 0;} .nav ul li a { } .nav ul li a:hover{ text-decoration: underline;} |
Кроме непосредственно фиксации меню при прокрутке есть CSS для размещение ссылок в одну строку.
jQuery("document").ready(function($){ var nav = $('.nav-container'); $(window).scroll(function () { if ($(this).scrollTop() > 136) { nav.addClass("f-nav"); } else { nav.removeClass("f-nav"); } }); }); |
Логика работы функций следующая — когда пользователь находится или прокручивает страницу ниже 136 пикселей сверху, то для меню добавляется класс f-nav, а в нем прописаны знакомые нам position:fixed и top:0.
Значение в 136 пикселей можно менять в зависимости от вашего дизайна. Кроме того, если вы интегрируете данный пример в свое готовое горизонтальное фиксированное меню на сайте, то указывайте соответствующие значения классов в JS и CSS. Если у вас возникли какие-то нюансы с реализацией кода, загляните в комментарии к оригинальной статье — там есть парочка подсказок.
Напоследок предлагаю ознакомиться с еще одним вариантом реализации нашей задачи. Не буду особо детализировать его, просто размещаю вставку из онлайн редактора кода Codepen.
Прокрутка до якоря с фиксированным меню
Для начала пару слов о самой задаче. Допустим у вас есть сайт, где вы решили фиксировать меню при прокрутке. При этом в навигации или тексте используются так называемые якоря (Anchor). При переходе по этим ссылкам экран страницы автоматически перемещается к соответствующим местам на странице (где расположен якорь). Однако в таком случае часть контента закрывается блоком меню.
Данная ситуация чаще всего возникает в лендингах. На скриншоте проблема заметна более наглядно:
На StackOverflow найдено два решения задачи, которые отличаются лишь CSS.
Чтобы создать якорь с отступом первым делом добавим ему определенный стиль, например, anchor:
<h1><a class="anchor" name="linktext">Some text</a></h1> |
Далее для него в стилях прописываете:
.anchor{ display:block; height:55px; margin-top:-55px; visibility:hidden; } |
Здесь 55 пикселей — высота меню + отступ, которые нужны чтобы текст контента был виден. Указывайте значения, подходящие вашему сайту. В работе я применял именно этот вариант.
Альтернативный метод предлагает реализацию через padding, там вообще получается одна строка кода:
.anchor { padding-top: 90px; } |
В дополнение предлагаю глянуть эту заметку где автор привел сразу 5 разных решений как правильно использовать якоря с отступом для горизонтального фиксированного меню при прокрутке страницы: через псевдоэлементы, негативные отступы и т.п.
Динамическое фиксированное меню + якоря содержания (в мобильной версии)
Уже по заголовку видно, что ситуация у меня возникла не простая. Возможно, и существует какое-то более изящное решение, но за день поисков ничего лучше найти не удалось. Оно еще и работает в отличии от остальных!
Пару слов о самой задаче. Вот как выглядит мое меню:
Здесь:
- Во-первых, в начале статьи с помощью плагина Easy Table of Contents автоматически выводится содержимое той или иной публикации. При клике на соответствующие ссылки вы попадаете в нужное место статьи. ВАЖНО! Если у вас установлен этот же модуль, то там для мобильных версий есть специальная настройка и ничего дополнительно внедрять НЕ нужно.
- Во-вторых, на мобильных устройствах при прокрутке экрана появляется специальное меню с темным фоном, которое фиксируется сверху экрана.
Я перепробовал много разных вариантов: и рассмотренный выше сдвиг якоря вниз страницы с помощью CSS, и добавление специальной переменной, срабатывающей при клике на содержимое Easy Table of Contents, но безрезультатно.
Сложность была в том, что оба действия (якорь и появление мобильного меню) были привязаны к обработке события scroll. Я пытался их разделить, но переход на якорные ссылки все равно влиял на scroll. Читал, что можно сделать подобные якоря без перегрузки страницы с помощью hashchange, но в эту тему не сильно вникал (хотя в примере ниже эта фишка присутствует).
В итоге мне помогла эта запись на Stackoverflow. Ниже представлена версия с jQuery для более гладкого перехода. Визуально решение позволяет после перехода на якорь “отмотать” назад содержимое экрана чтобы меню его не перекрывало.
Вот работающий финальный код + возможность посмотреть результат в реальном времени:
Продублирую на всякий случай его в статье. По HTML ничего особенного – сначала идет меню, а затем 5 разных блоков DIV.
<div id="fixed"> My fixed bar </div> <div id="targets"> <div id="target-1"> <a href="#target-2">Jump to target 2</a> </div> <div id="target-2"> <a href="#target-3">Jump to target 3</a> </div> <div id="target-3"> <a href="#target-4">Jump to target 4</a> </div> <div id="target-4"> <a href="#target-5">Jump to target 5</a> </div> <div id="target-5"> <a href="#target-1">Jump to target 1</a> </div> </div> |
Стили тоже вполне стандартные – каждый DIV занимает всю высоту видимой области экрана + прописываются детали фиксированного меню.
body { padding: 0; margin: 50px 0 0; font-family: "Arial"; font-size: 1em; } a { color: #333; } #fixed { position: fixed; height: 50px; line-height: 50px; background: #000; top: 0; left: 0; right: 0; color: #fff; padding-left: 5px; } #targets div { height: 100vh; padding: 10px; color: #333; } #target-1 { background: #888; } #target-2 { background: #999; } #target-3 { background: #AAA; } #target-4 { background: #BBB; } #target-5 { background: #CCC; } |
Ну, и в завершение – JavaScript. Тут, признаться я мало что могу объяснить, т.к. слабо понимаю, что конкретно происходит. Если бы не комментарии разработчика, то все это походило бы на какую-то магию.
(function(document, history, location) { var HISTORY_SUPPORT = !!(history && history.pushState); var anchorScrolls = { ANCHOR_REGEX: /^#[^ ]+$/, OFFSET_HEIGHT_PX: 50, /** * Establish events, and fix initial scroll position if a hash is provided. */ init: function() { this.scrollToCurrent(); $(window).on('hashchange', $.proxy(this, 'scrollToCurrent')); $('body').on('click', 'a', $.proxy(this, 'delegateAnchors')); }, /** * Return the offset amount to deduct from the normal scroll position. * Modify as appropriate to allow for dynamic calculations */ getFixedOffset: function() { return this.OFFSET_HEIGHT_PX; }, /** * If the provided href is an anchor which resolves to an element on the * page, scroll to it. * @param {String} href * @return {Boolean} - Was the href an anchor. */ scrollIfAnchor: function(href, pushToHistory) { var match, anchorOffset; if(!this.ANCHOR_REGEX.test(href)) { return false; } match = document.getElementById(href.slice(1)); if(match) { anchorOffset = $(match).offset().top - this.getFixedOffset(); $('html, body').animate({ scrollTop: anchorOffset}); // Add the state to history as-per normal anchor links if(HISTORY_SUPPORT && pushToHistory) { history.pushState({}, document.title, location.pathname + href); } } return !!match; }, /** * Attempt to scroll to the current location's hash. */ scrollToCurrent: function(e) { if(this.scrollIfAnchor(window.location.hash) && e) { e.preventDefault(); } }, /** * If the click event's target was an anchor, fix the scroll position. */ delegateAnchors: function(e) { var elem = e.target; if(this.scrollIfAnchor(elem.getAttribute('href'), true)) { e.preventDefault(); } } }; $(document).ready($.proxy(anchorScrolls, 'init')); })(window.document, window.history, window.location); |
У меня лично код сработал без каких-либо дополнительных правок и манипуляций – я просто добавил его в файл скриптов своего шаблона script.js. Важно заменить в коде переменную OFFSET_HEIGHT_PX, в которой задается высота вашего меню (т.е. итоговый отступ сверху экрана).
Плагин Page scroll to id
Кстати, если вы работаете с WordPress, то вас может заинтересовать этот модуль. Нашел его чуть раньше, и он частично перекликается с текущей задачей. Плагин внедряет более плавную и красивую анимацию перехода по ссылкам якорям и при этом обладает некоторыми приятными фишками:
- вертикальным и горизонтальным скроллингом;
- разными настройками анимации;
- поддержкой переходов по ссылкам на другие страницы;
- подсветкой ссылок, заданием отступов, длительности перехода и т.п.;
Вот как выглядит пример меню:
Думаю, Page scroll to id будет полезен, если вам надо сделать навигацию на одностраничном лендинге, а базовый шаблон не обладает подобной опцией (либо она ограничена в настройках).
Если знаете еще какие-то интересные сниппеты или есть, что добавить по теме, пишем в комментариях.
Утащил себе второй вариант с jQuery и CSS, пока все работает норм. Спасибо.
Ни фига он не работает. Перенес один к одному на чистую страницу. Все ему «фиолетово».
Как уезжало меню, так и уезжает.
Полная хренатень. Речь о втором скрипте.
Олег, во-первых, можете попробовать скрипт с исходника (там есть ссылка на оригинальную статью). Вторая мысль — возможно, с 2012 кое-что все же поменялось и работа кода нарушилась, хотя я лично тестировал все около года назад и проблем не было. В крайнем случае придется воспользоваться другими сниппетами — третий (там где пример в CodePen) точно рабочий, как видите.
это работает и по сей день. речь про второй скрипт))
Андрей, спасибо за фидбек.
Андрей, подскажите пожалуйста по отступу от якоря.
Переход с другой страницы
<a href="oformlenie.html#1" rel="nofollow ugc">01-12</a>
На странице стоит якорь id=»1″
Менюшка перекрывает якорь и какой метод тут проканает? Я уже устал лопатить нэт…
Михаил, недавно была задача для WP с Елементором, сработал метод с обычными стилями (пункт «Прокрутка до якоря с фиксированным меню»). Кстати, если переход с других страниц, советую ставить ссылку полностью с названием сайта и полным URL.