Онлайн заработок, создание и монетизация сайтов, веб-разработка, SEO и SMO продвижение, фриланс, партнерки, полезные сервисы вебмастерам, блоггинг.

Wordpress шаблоны
RotaPost - Эффективная реклама в блогах
Главная » Web разработка » Верстка » Фиксированное меню на CSS, jQuery + использование якорей

Создаем фиксированное меню на CSS, jQuery + решение проблемы с якорями

Фиксированное менюС момента публикации первой версии данной заметки прошло уже несколько лет, но текущая информация все еще актуальна. Просто хочу дополнить ее разбором одной нетипичной и сложной ситуации, с которой на днях столкнулся по фрилансу. Новичкам озвученные здесь приемы точно пригодятся, плюс запишу их для себя, т.к. похожие задачи возникают время от времени.

В статье я не буду вдаваться в подробности всех деталей кода, укажу лишь основные нюансы. Можете скопировать эти примеры и попрактиковаться с ними на своих проектах, ну а полным новичкам — не помешает дополнительно глянуть сервис обучения верстке Interneting is Hard.

В посте представлены такие вопросы:

  1. Как фиксировать меню при прокрутке страницы (это когда блок навигации «прижимается» к верху экрана). Рассмотрим:
  2. Прокрутка до ссылки якоря с фиксированным меню — без дополнительных хаков в этом случае не обойтись.
  3. Мобильное фиксированное меню + ссылки якоря (оригинальная задачка с универсальным решением).
  4. Плагин 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;
}

В результате получится такая картинка:

Создание фиксированного меню на CSS

Ничего оригинального, но главное, что работает. Дабы увидеть эффект фиксированного меню при прокрутке страницы вам нужно будет добавить больше текста в блок контента (чтобы появилась полоса прокрутки).

Основные детали кода выше, как я уже говорил, это  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

Плагин Page scroll to id

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

  • вертикальным и горизонтальным скроллингом;
  • разными настройками анимации;
  • поддержкой переходов по ссылкам на другие страницы;
  • подсветкой ссылок, заданием отступов, длительности перехода и т.п.;

Вот как выглядит пример меню:

Фиксированное меню

Думаю, Page scroll to id будет полезен, если вам надо сделать навигацию на одностраничном лендинге, а базовый шаблон не обладает подобной опцией (либо она ограничена в настройках).

Если знаете еще какие-то интересные сниппеты или есть, что добавить по теме, пишем в комментариях.

16.10.20

Категории: Верстка.

Теги: , , , ,

3 Comments
  1. Vitaly78

    Утащил себе второй вариант с jQuery и CSS, пока все работает норм. Спасибо.

  2. Олег

    Ни фига он не работает. Перенес один к одному на чистую страницу. Все ему «фиолетово».
    Как уезжало меню, так и уезжает.
    Полная хренатень. Речь о втором скрипте.

  3. Tod

    Олег, во-первых, можете попробовать скрипт с исходника (там есть ссылка на оригинальную статью). Вторая мысль — возможно, с 2012 кое-что все же поменялось и работа кода нарушилась, хотя я лично тестировал все около года назад и проблем не было. В крайнем случае придется воспользоваться другими сниппетами — третий (там где пример в CodePen) точно рабочий, как видите.

Добавить комментарий

Ваш e-mail не публикуется. Обязательные поля помечены *
Если вы комментируете впервые, то текст будет отправлен на модерацию.