Построение адаптивного навигационного меню на JavaScript

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

Концепция адаптивного веб-дизайна (Responsive Web Design, RWD) оказала огромное влияние на способы построения сайтов и приложений для просмотра на разных устройствах. Повсюду появляются новые техники, инструменты и взгляды на адаптивный веб-дизайн.

RWD даёт нам возможность предоставить пользователям одинаковый комфорт в работе с нашими сайтами на всём разнообразии имеющихся у них устройств.

Статья Этана Маркота подчёркивает три составных части адаптивного дизайна: резиновая сетка, масштабируемые на лету изображения и медиа-запросы (media query) в CSS3.

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

Многие, включая меня, могли бы заметить, что веб-контент должен быть доступен для всех пользователей, в том числе и для тех, чьи браузеры не поддерживают JavaScript. Здесь мы как разработчики должны понимать тонкую грань между использованием скриптов для повышения удобства пользования сайтом и прятаньем контента за стеной JS.

Исходные файлы примеров, приводимых в этой статье.

Если вам приходилось писать JavaScript-код для RWD, вы наверняка встречали ссылки на работы Скотта Джела из Filament Group. Нам повезло, что такие люди, как Скотт, делятся с нами своими инструментами и таким образом способствуют развитию адаптивного дизайна.

Давайте что-нибудь построим

Я не измерял, сколько рабочего времени у нас ушло на каждую часть нашего сайта, но обычно мы посвящаем навигации больше внимания, чем любому другому аспекту дизайна. Особенно при разработке больших сайтов. Убедиться в том, что контент расположен на своём месте, хорошо организован и легко доступен с любого устройства, которое попадёт нам в руки – задача не на пять минут, мягко говоря.

(Используйте JQuery, чтобы иметь доступ к классам и обработчикам событий независимо от недоработок отдельных браузеров.)

Давайте что-нибудь построим

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

  • Хорошо работать на маленьких и больших экранах;
  • Работать в браузерах Chrome, Safari, Firefox, а также IE версии 8 и выше;
  • Работать с JavaScript или без него.

Разметка

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

Так что обратимся прямо к файлу index.html и начнём разбор.

В подвале?

Взглянув на HTML-содержимое, вы наверняка задались вопросом: «Почему навигация расположена в подвале?» Хороший вопрос. Нашей целью было сделать навигацию доступной в браузерах без поддержки JavaScript или с отключенным JavaScript.

На маленьком экране без JS навигация съест всё пространство страницы, если будет располагаться сверху. Поэтому в шапке мы оставили только ссылку на навигацию, разворачивающуюся в самом низу страницы – в подвале.

(Навигация на маленьком экране во всплывающем элементе, появляющемся при клике по иконке меню.)

В подвале?

Малыши – вперёд

Строить адаптивный сайт намного проще, если начать с самых маленьких экранов и постепенно добавлять фичи для больших размеров.

На начальном этапе у нас должна быть навигация, работоспособная на маленьком экране без скриптов, и ссылка «Показать навигацию» (ShowNav), появляющаяся на малых экранах только при наличии JavaScript, показывающая простой hover-эффект и пока бесполезная.

Давайте вдохнём немного JS-жизни в навигацию на маленьких экранах. Добавим ссылки на JQuery и наши навигационные скрипты прямо перед тэгом нашего файла index.html:

<script src="js/jquery.min.js"></script>
<script src="js/nav.js"></script>

Начнём работу с файла nav.js. Создадим в нём объект window.NAV, который будет содержать весь код управления навигацией:

(function() {
  window.NAV = {
    $body: $("body"),
    $subMenus: $(".subMenu"),
    toggle: function(e) {
      e.preventDefault();
      NAV.$body.toggleClass("mainMenu-is-open");
    },
    bindEvents: function() {
      $(".js-togglesOffCanvas").on("click", NAV.toggle);
    },
    init: function() {
      NAV.bindEvents();
    }
  }
})();
NAV.init();

Метод init() производит все необходимые настройки. Он вызывает NAV.bindEvents(), который при помощи JQuery назначает обработчик NAV.toggle() на событие onclick всех элементов класса js-togglesOffCanvas.

NAV.toggle() отменяет стандартную обработку onclick, чтобы пользователь не переходил по ненужным ссылкам, и, используя JQuery, применяет класс mainMenu-is-open к телу документа или, наоборот, удаляет его.

Это устанавливает правила CSS, выдвигающие блок .mainNav, скрытый за левой частью экрана, в поле зрения пользователя с использованием CSS3-трансформации.

Использование translate3d принудительно включает аппаратное ускорение в WebKit. Мы можем использовать Modernizr, чтобы определить возможности платформы, а затем – translate3d для более плавной анимации.

(Подсвечиваем всплывающий блок при помощи JavaScript. Событие onclick на этом блоке вызывает закрытие меню).

Малыши – вперёд

Поскольку наш обработчик запускается элементами класса js-togglesOffCanvas, мы должны добавить в этот класс навигационную ссылку в заголовке:

<a href="#navigation" class="showNav js-togglesOffCanvas">Show Nav</a>

Теперь у нас есть навигационная панель, которая выдвигается слева, если мы работаем на экране малого разрешения с разрешённым JavaScript. Но что это? Мы никак не можем закрыть навигацию! Надо это исправить.

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

Мы не будем пытаться повесить обработчики onclick на всё подряд, а просто перекроем правую часть экрана прозрачным блоком <div> и будем обрабатывать клик по нему. Создадим JS-переменную для хранения разметки и добавим её к объекту NAV:

window.NAV = {
$clickOverlay: $("<div class=&rsquo;clickOverlay js-togglesOffCanvas&rsquo;></div>"),
&hellip;

Теперь добавим в метод init() строчку, которая добавит этот блок <div> к документу. Таким образом, этот оверлейный блок будет присутствовать в нашем документе, только если JavaScript разрешён:

init: function() {
  NAV.$clickOverlay.appendTo("body");
  NAV.bindEvents();
}

Если вы хотите видеть этот блок <div> в процессе отладки, добавьте ему класс visible. Теперь самое время добавить в меню кнопку закрытия (<button>) прямо в начало блока <div class="mainNav">:

<button class="js-togglesOffCanvas closeOffCanvas">
<span class="visuallyHidden">Close Menu</a>
</button>

Теперь мы можем закрыть навигационную панель. Осталась ещё одна задача. Мы не можем добраться до ссылок второго уровня. Мы исправим это при помощи некоторых изменений в файле nav.js. Для начала добавим к объекту NAV следующий метод:

toggleSubNav: function(e) {
  e.preventDefault();
  $(this).siblings("ul").stop().slideToggle("fast");
},

Теперь добавим к NAV.bindEvents обработчик для js-togglesSubMenu:

bindEvents: function() {
  $(".js-togglesOffCanvas").on("click", NAV.toggle);
  $(".mainNav").on("click", ".js-togglesSubMenu",NAV.toggleSubNav);
},

И наконец, добавим в класс js-togglesSubMenu все ссылки из файла index.html, которые должны показывать подменю:

<li><a href="#" class="js-togglesSubMenu">Products</a>

Теперь навигация вполне работоспособна, но если вы немного поиграете с ней, то обнаружите несколько недоделок.

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

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

(Всплывающая навигация полностью открыта и показывает меню второго уровня.)

Малыши – вперёд - 2

Пока что в браузерах нет событий, запускающих скрипт при изменении параметров экрана. Но можно определить текущее состояние CSS медиа-запроса при помощи matchMedia API. Для таких случаев я использую обёртку mediaCheck, которую я написал для работы с matchMedia.

Она позволяет запустить скрипт при изменении активности определённого медиа-запроса. Чтобы использовать эту обёртку, добавим ссылку на mediaCheck непосредственно перед включением nav.js:

<script src="js/mediaCheck.js"></script>

Затем добавим код, который сообщит mediaCheck, какие именно медиа-запросы нам нужно отслеживать, и что делать при изменении их состояния. Эти строчки надо внести в конец функции init():

mediaCheck({
  media: "(min-width: 35em)",
  entry: function() {
    NAV.clear();
  }
});

Наконец, напишем метод NAV.clear(). Уберём <body> из класса mainMenu-js-open, сбросим положение элементов, чтобы освободить место для нормального расположения навигации, и уберём стили анимации, которые добавили для различных элементов:

window.NAV = {
  $subMenus: $(".subMenu"),
  clear: function() {
    NAV.$body.removeClass("mainMenu-is-open");
    NAV.$subMenus.removeAttr("style");
  }
&hellip;

Теперь, когда ширина браузера становится равной или превосходит 35 em, сайт возвращается к нормальному стилю навигации.

(Устройства слева направо: Apple iPad, Apple iPhone 5, Blackberry Z10.)

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

Добавим следующий метод в window.NAV:

toggleAnimations: function() {
  if ( APP.getState() === "small" ) {
    NAV.$body.addClass("enableAnimations");
  } else {
    NAV.$body.removeClass("enableAnimations");
  }
},

Доработаем вызов mediaCheck в методе init():

mediaCheck({
  media: "(min-width: 30em)",
  entry: function() {
    NAV.clear();
    NAV.toggleAnimations();
  },
  exit: function() {
    NAV.toggleAnimations();
  }
});

И наконец, изменим файл base.css (со строки 80):

.enableAnimations .mainNav, .enableAnimations .mainContent,
.enableAnimations .masthead, .enableAnimations .clickOverlay{

Теперь все переходные эффекты ограничены классом enableAnimations, который накладывается на нужные элементы только при малом разрешении экрана.

Третья недоработка заметна, если, переключив экран в большой размер, кликнуть по пункту «Products» и вызвать его подменю. Обработчик клика, который мы создали для экрана малого размера, всё ещё срабатывает.

(Modernizr предоставляет способ определить возможности платформы и применить соответствующие JS- и CSS-приёмы).

Починить это не так-то просто. Я здорово улучшил своё первоначальное решение проблемы при содействии Адама Симпсона. Посмотрите на файл app.js в архиве с исходными материалами для нашего урока, и вы заметите определение метода APP.getState(), который внедряет в страницу элемент с ID=sizeTest.

Применяемые к этому элементу стили позволяют скриптам понять, какой размер имеет текущее окно браузера относительно определённых медиа-запросов. Включите app.js в файл index.html:

<script src="js/app.js"></script>

В CSS вы увидите следующее (выглядит непонятно):

#sizeTest {
  font-size: 10px;
}
@media (min-width: 30em) {
  #sizeTest {
  font-size: 30px;
}
}

APP.getState проверяет размер шрифта элемента sizeTest и возвращает предопределённую строку: small или large. Мы пробовали другие решения, но это победило по соображениям кроссбраузерности.

Возвращаемое методом значение может быть использовано в управлении логикой наших скриптов. Например, можно изменить NAV.toggleSubNav() следующим образом:

toggleSubNav: function(e) {
  e.preventDefault();
  if ( APP.getState() === "small" ) {
    $(this).siblings("ul").stop().slideToggle("fast");
  }
}

Теперь скриптовое переключение подменю происходит только на малых разрешениях экрана.

Заключение

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

РедакцияПеревод статьи «Build a responsive Javascript nav menu»