Адаптивная навигация

Навигация - одна из основных составляющих сайта. Она должна быть доступна и удобна для использования на любом устройстве.

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

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

bg

Логика адаптивной навигации

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

Когда десктоп навигация не помещается в размеры окна браузера, скрываем её при необходимой ширине, используя @media запросы, и отображаем “гамбургер” иконку

Подробнее про адаптивную вёрстку - Основы адаптивной вёрстки

Для начала разберем базовую логику, которую будем использовать для мобильной навигации. В примерах ниже HTML-разметка и CSS-стили только для наглядности, пока что всё внимание на логику в JavaScript


Вариант 1

Первый вариант с двумя элементами управления - отдельная кнопка для открытия навигации, и отдельная для закрытия.

Такой подход применяется, если окно навигации при открытии перекрывает один из элементов управления. Например, окно навигации во весь экран - в таком случае кнопка открытия будет, например, в <header>, а кнопка закрытия в самом блоке мобильной навигации

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <div class="header">
      <div class="container">
        <div class="header__buttons buttons">
          <div class="buttons__button buttons__button_open">Open</div> <!-- кнопка для открытия мобильной навигации -->
          <div class="buttons__button buttons__button_close">Close</div><!-- кнопка для закрытия мобильной навигации -->
        </div>
      </div>
    </div>
    <div class="mobile"> <!-- скрытая мобильная навигация -->
      <div class="container">
        <div class="mobile__nav">
          <ul>
            <li><a href="">Home</a></li>
            <li><a href="">Portfolio</a></li>
            <li><a href="">About</a></li>
            <li><a href="">Contact</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div class="content">
      <div class="container">
        <div class="content__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque nam, nostrum quaerat explicabo a voluptates sit totam debitis accusamus reprehenderit ea, deserunt cupiditate, aliquid aliquam vero rerum placeat enim quos!</div>
      </div>
    </div>
    <script src="js/main.js"></script>
  </body>
</html>


Мобильная навигация .mobile изначально скрыта свойством display: none;. Сделаем дополнительный класс mobile_active со свойством display: block;, при добавлении которого навигация .mobile будет отображаться

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.header {
  padding: 15px 0;
}
.buttons {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.buttons__button {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background: #7371fc;
  padding: 15px 30px;
  color: #fff;
  border-radius: 15px;
  cursor: pointer;
}
.buttons__button:not(:last-child) {
  margin-right: 15px;
}
.mobile {
  background: #7371fc;
  display: none; /* изначально мобильная навигация скрыта */
}
.mobile_active {
  display: block; /* при добавлении класса mobile_active мобильная навигация отображается */
}
.mobile__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
  padding: 15px 0;
  text-align: right;
}
.mobile__nav ul li:not(:last-child) {
  margin-bottom: 10px;
}
.mobile__nav ul li a {
  color: #fff;
  text-decoration: none;
}
.content {
  padding: 30px 0;
}


При клике на кнопку .buttons__button_open' для блока навигации .mobile добавляем класс mobile_active, после чего навигация отображается

При клике на кнопку .buttons__button_close' для блока навигации .mobile удаляем класс mobile_active, после чего навигация скрывается

В блоке с JavaScript кодом подробные комментарии

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодейтсвию

  const mobile = () => { // объявляем основную функцию, чтобы вся логика мобильной навигации находилась в одном месте

    const open = document.querySelector('.buttons__button_open') // находим кнопку Open
    const close = document.querySelector('.buttons__button_close') // находим кнопку Close
    const mobile = document.querySelector('.mobile') // находим блок мобильной навигации

    open.addEventListener('click', () => { // при клике на кнопку Open
      mobile.classList.add('mobile_active') // показываем навигацию, добавляя класс mobile_active со свойством display: bloсk;
    })
    close.addEventListener('click', () => { // при клике на кнопку Close
      mobile.classList.remove('mobile_active') // скрываем навигацию, удаляя класс mobile_active
    })

  }

  mobile() // вызываем основную функцию

})

Посмотреть на Codepen


Вариант 2

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

Такой подход применяется, если окно навигации при открытии не перекрывает элемент управления. Например, окно навигации при открытии располагается ниже <header> - в таком случае кнопка не перекрывается и может отвечать как за открытие, так и за закрытие навигации

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <div class="header">
      <div class="container">
        <div class="header__buttons buttons">
          <div class="buttons__button">Menu</div> <!-- кнопка для открытия/закрытия мобильной навигации -->
        </div>
      </div>
    </div>
    <div class="mobile"> <!-- скрытая мобильная навигация -->
      <div class="container">
        <div class="mobile__nav">
          <ul>
            <li><a href="">Home</a></li>
            <li><a href="">Portfolio</a></li>
            <li><a href="">About</a></li>
            <li><a href="">Contact</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div class="content">
      <div class="container">
        <div class="content__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque nam, nostrum quaerat explicabo a voluptates sit totam debitis accusamus reprehenderit ea, deserunt cupiditate, aliquid aliquam vero rerum placeat enim quos!</div>
      </div>
    </div>
    <script src="js/main.js"></script>
  </body>
</html>


Мобильная навигация .mobile изначально скрыта свойством display: none;. Сделаем дополнительный класс mobile_active со свойством display: block;, при добавлении которого навигация .mobile будет отображаться

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.header {
  padding: 15px 0;
}
.buttons {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.buttons__button {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background: #7371fc;
  padding: 15px 30px;
  color: #fff;
  border-radius: 15px;
  cursor: pointer;
}
.buttons__button:not(:last-child) {
  margin-right: 15px;
}
.mobile {
  background: #7371fc;
  display: none; /* изначально мобильная навигация скрыта */
}
.mobile_active {
  display: block; /* при добавлении класса mobile_active мобильная навигация отображается */
}
.mobile__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
  padding: 15px 0;
  text-align: right;
}
.mobile__nav ul li:not(:last-child) {
  margin-bottom: 10px;
}
.mobile__nav ul li a {
  color: #fff;
  text-decoration: none;
}
.content {
  padding: 30px 0;
}


В данном случае при клике на единственную кнопку <div class="buttons__button">Menu</div> переключаем класс mobile_active у блока навигации c помощью метода toggle() - если есть класс mobile_active - то удаляем, если нет - то добавляем

В блоке с JavaScript кодом подробные комментарии

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодейтсвию

  const mobile = () => { // объявляем основную функцию, чтобы вся логика мобильной навигации находилась в одном месте

    const menu = document.querySelector('.buttons__button') // находим кнопку Menu
    const mobile = document.querySelector('.mobile') // находим блок мобильной навигации

    menu.addEventListener('click', () => { // при клике на кнопку Menu
      mobile.classList.toggle('mobile_active') // показываем/скрываем навигацию, добавляя/удаляя класс mobile_active со свойством display: bloсk;
    })

  }

  mobile() // вызываем основную функцию

})

Посмотреть на Codepen


Вариант 3

Третий вариант также с одним элементом управления и схож со вторым вариантом, но для более тонкой настройки мобильной навигации, вместо метода toggle(), будем проверять наличие класса mobile_active через метод contains() и в зависимости от этого либо добавлять, либо удалять его, а также прописывать дополнительную логику, например, добавлять активный класс для элемента управления

Такой подход также применяется, если окно навигации при открытии не перекрывает элемент управления. Например, окно навигации при открытии располагается ниже <header> - в таком случае кнопка не перекрывается и может отвечать как за открытие, так и за закрытие навигации

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>
    <div class="header">
      <div class="container">
        <div class="header__buttons buttons">
          <div class="buttons__button">Menu</div> <!-- кнопка для открытия/закрытия мобильной навигации -->
        </div>
      </div>
    </div>
    <div class="mobile"> <!-- скрытая мобильная навигация -->
      <div class="container">
        <div class="mobile__nav">
          <ul>
            <li><a href="">Home</a></li>
            <li><a href="">Portfolio</a></li>
            <li><a href="">About</a></li>
            <li><a href="">Contact</a></li>
          </ul>
        </div>
      </div>
    </div>
    <div class="content">
      <div class="container">
        <div class="content__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque nam, nostrum quaerat explicabo a voluptates sit totam debitis accusamus reprehenderit ea, deserunt cupiditate, aliquid aliquam vero rerum placeat enim quos!</div>
      </div>
    </div>
    <script src="js/main.js"></script>
  </body>
</html>


Мобильная навигация .mobile изначально скрыта свойством display: none;. Сделаем дополнительный класс mobile_active со свойством display: block;, при добавлении которого навигация .mobile будет отображаться

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.header {
  padding: 15px 0;
}
.buttons {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
.buttons__button {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background: #7371fc;
  padding: 15px 30px;
  color: #fff;
  border-radius: 15px;
  cursor: pointer;
}
.buttons__button:not(:last-child) {
  margin-right: 15px;
}
.mobile {
  background: #7371fc;
  display: none;
}
.mobile_active {
  display: block;
}
.mobile__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
  padding: 15px 0;
  text-align: right;
}
.mobile__nav ul li:not(:last-child) {
  margin-bottom: 10px;
}
.mobile__nav ul li a {
  color: #fff;
  text-decoration: none;
}
.content {
  padding: 30px 0;
}


В данном случае при клике на единственную кнопку <div class="buttons__button">Menu</div> проверяем, содержит ли блок навигации .mobile класс mobile_active, если не содержит, то добавляем его, если содержит, то убираем.

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

В блоке с JavaScript кодом подробные комментарии

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодейтсвию

  const mobile = () => { // объявляем основную функцию, чтобы вся логика мобильной навигации находилась в одном месте

    const menu = document.querySelector('.buttons__button') // находим кнопку Menu
    const mobile = document.querySelector('.mobile') // находим блок мобильной навигации

    menu.addEventListener('click', () => { // при клике на кнопку Menu
      if (!mobile.classList.contains('mobile_active')) { // если мобильная навигация еще не содержит класс mobile_active, то есть не активна
        mobile.classList.add('mobile_active') // показываем навигацию, добавляя класс mobile_active со свойством display: bloсk;
        // menu.classList.add('button_active') - здесь можно добавить активный класс для элемента управления
      } else { // а если мобильная навигация уже имеет класс mobile_active, то есть активно
        mobile.classList.remove('mobile_active') // скрываем навигацию, удаляя класс mobile_active со свойством display: bloсk;
        // menu.classList.remove('button_active') здесь можно удалить активный класс для элемента управления
      }
    })

  }

  mobile() // вызываем основную функцию

})

Посмотреть на Codepen



Более сложный пример адаптивной навигации

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

В разметке будут присутствовать шапка сайта (header) с лого и десктоп навигацией, элемент затемнения области контента (overlay), блок с мобильной навигацией, и контентная часть для наглядности

В данном примере используем CSS библиотеку Hamburgers

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/hamburgers.min.css"/> <!-- подключаем CSS библиотеку Hamburgers -->
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>

    <header class="header"> <!-- шапка сайта -->
      <div class="container">
        <div class="header__flex">
          <div class="header__logo"><a href="">Logo</a></div> <!-- логотип -->
          <nav class="header__nav"> <!-- десктоп навигация -->
            <ul>
              <li><a href="">Home</a></li>
              <li><a href="">About</a></li>
              <li><a href="">Portfolio</a></li>
              <li><a href="">Contact</a></li>
            </ul>
          </nav>
          <div class="header__hamburger header__hamburger_open"> <!-- элемент для открытия мобильной навигации -->
            <button class="hamburger hamburger--slider-r" type="button"><span class="hamburger-box"><span class="hamburger-inner"></span></span></button>
          </div>
        </div>
      </div>
    </header>

    <div class="overlay"></div> <!-- блок затемнения области контента -->

    <div class="mobile"> <!-- мобильная навигация -->
      <div class="mobile__hamburger mobile__hamburger_close"> <!-- элемент для закрытия мобильной навигации -->
        <button class="hamburger hamburger--slider-r is-active" type="button"><span class="hamburger-box"><span class="hamburger-inner"></span></span></button>
      </div>
      <div class="mobile__nav">
        <ul>
          <li><a href="">Home</a></li>
          <li><a href="">About</a></li>
          <li><a href="">Portfolio</a></li>
          <li><a href="">Contact</a></li>
        </ul>
      </div>
    </div>

    <section class="content">
      <div class="container">
        <div class="content__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe maxime in, quas natus porro? Porro eligendi molestiae quam iure, enim architecto cum dicta assumenda provident sint nobis debitis accusamus consequuntur doloremque consectetur! Modi quae maiores quod culpa! At mollitia, consequatur iste officia laborum beatae accusantium temporibus veniam fugiat esse maxime enim quo voluptate dolorem possimus accusamus quibusdam incidunt, architecto doloremque, autem iure corrupti quaerat excepturi hic. Beatae totam reiciendis accusantium ut aspernatur deserunt, tempore sit repellendus commodi assumenda pariatur. Amet voluptates at vitae molestias exercitationem, obcaecati suscipit id mollitia corrupti deleniti a earum quae dicta laboriosam esse sequi culpa eveniet adipisci repudiandae quisquam architecto ratione. Tempore consectetur dolorum labore quas dolores facilis sint minus illo. Adipisci distinctio accusamus animi magni autem alias, maiores blanditiis, error cumque explicabo veritatis praesentium recusandae placeat? Excepturi, distinctio, velit? Impedit ducimus fuga quisquam veniam quam nihil odit, similique, facere optio voluptates hic vel, iusto doloribus. Quia reiciendis, corrupti, culpa consequuntur consequatur qui, natus earum sint maiores fugiat labore ipsam eum! Est enim aliquid earum non eius, laboriosam quidem? Nulla, nam quis perspiciatis explicabo molestias cumque itaque odio laborum id veniam labore nihil, similique rem velit eum deserunt quaerat, inventore aspernatur sit accusamus nesciunt assumenda architecto neque dolorem eveniet. Explicabo dolore itaque, minima vitae, consectetur assumenda nostrum suscipit, iste distinctio ipsum, dolor fugit. Illum mollitia sequi aut dignissimos! Enim voluptas voluptatum, ullam, architecto a ipsa numquam aspernatur obcaecati voluptate quae est ad quaerat deserunt incidunt dolore molestias dolorem minus soluta, eius magni ex? Optio voluptatem, quam omnis harum ad maiores deleniti autem quia rerum tempora enim voluptas ut! Cupiditate voluptatum, nulla assumenda fugiat perferendis in amet, vero minima quod at hic magni doloremque. Neque atque, quo necessitatibus nostrum, doloribus temporibus. Laborum odit deserunt dignissimos soluta quae vel maiores doloribus accusantium asperiores quisquam, blanditiis non nemo voluptate natus fugit eius impedit adipisci dolore mollitia voluptatum. Aliquid molestiae molestias asperiores non ducimus fugiat in inventore. In deserunt, expedita. Consequuntur perspiciatis quisquam, saepe harum eum nihil sequi facere doloribus sunt. Inventore ipsum officia dolore reprehenderit deleniti repellendus animi, molestiae rerum quo, similique amet ex blanditiis itaque rem, nemo ullam. Numquam rerum vitae nisi officiis maiores cupiditate incidunt quia nam qui cum, dolorem exercitationem hic voluptatem, fugiat quaerat. Quidem, a labore quibusdam quaerat eum earum rem eius consequuntur sapiente impedit eaque nostrum sit consectetur dignissimos iste dolores nisi omnis quasi repudiandae deserunt quam molestias, reprehenderit eos. Qui consectetur voluptatum, consequatur eligendi autem odit optio enim mollitia nesciunt veniam voluptatem, laboriosam. Placeat illo, magnam minus. Nesciunt quis minima doloremque quo, aliquid, vel aspernatur quasi quisquam atque? Atque illum consequuntur a quis consectetur quos fugit! Commodi neque tempore similique, vitae repellendus, nisi consequatur qui at delectus corporis distinctio atque earum facere beatae, nihil debitis dicta incidunt et expedita unde explicabo quasi veniam cumque. A dicta error eum odit est repellendus sit necessitatibus veritatis enim, maiores aliquid amet quidem excepturi id placeat sapiente fugit similique fuga aperiam tenetur optio vero, cumque debitis quia? Fugit reiciendis, facere ad. Et nisi sed molestias, recusandae reiciendis omnis iusto nihil? Quis corrupti fugit excepturi illum incidunt minima accusantium vitae. Aliquid voluptatibus aspernatur dicta totam nulla consequuntur eaque consectetur quasi pariatur, dolorum nemo atque amet ullam obcaecati commodi voluptatum quaerat sint odit corporis, cumque reprehenderit dolorem. Hic adipisci voluptatibus molestias eligendi. Unde illo necessitatibus sint repellat. Distinctio accusamus, hic exercitationem quos repudiandae minima dolorum sequi magni impedit obcaecati id eum rem porro dicta! Voluptates repellat est blanditiis eos molestias, explicabo! Dolores ad et voluptas a recusandae sint excepturi ipsa eaque, natus, eveniet sequi pariatur laborum dolorum repellendus ab nobis, autem accusamus maxime tempora quisquam dicta. Repellendus in veniam debitis sint quia expedita, unde hic, nisi ab tempore doloribus iure voluptatem nam dolore dignissimos! Est nostrum optio iste magni, minima rerum nihil. Eos quidem, nisi architecto ad, fugiat ullam tenetur labore voluptatibus consectetur illum voluptatum tempore, sapiente fugit nemo! Consectetur harum quasi, numquam facilis libero tempora, odit ratione suscipit id laudantium nisi maxime? Placeat corporis illo, recusandae obcaecati atque minima voluptatibus consequatur dolor exercitationem odit natus nisi harum repudiandae perferendis consectetur possimus tempore maiores at molestias, minus a. Autem officiis maxime velit possimus inventore facere provident accusamus excepturi, quaerat ea, minima ullam qui dicta quae quasi reiciendis nulla laborum ad aliquid dolorum veritatis. Ipsum praesentium, aspernatur quisquam dolore repellat iste nisi repellendus autem similique consectetur labore vero officia in sunt expedita architecto? Voluptas autem, vero eum! Aperiam voluptatum facilis libero assumenda iure facere earum, ea quis fuga expedita harum vel eos veniam est velit fugit omnis ipsa quos dicta minus dolores natus, quibusdam nihil. Dignissimos animi, cum, quis nemo ab, fugiat maxime laudantium dolores sint obcaecati voluptatibus ea. Fugiat minus explicabo sit natus consequuntur nihil ex dolore doloribus commodi ipsa quis, labore asperiores tempora aspernatur error atque quae animi esse, expedita quod molestiae! Cum maiores minima aut facere totam voluptate architecto voluptatem suscipit eum, odio repellat repudiandae ipsam nostrum sapiente repellendus nam animi aperiam molestiae optio deserunt ut quisquam! Voluptate blanditiis provident saepe laboriosam voluptates necessitatibus, velit ex recusandae eaque earum, nihil at sed, porro quae minus nam atque nesciunt impedit fuga praesentium iste totam natus dignissimos. Similique harum quia, vel fuga, maiores quam deserunt adipisci nostrum explicabo ipsam ducimus! Sapiente a, quidem, beatae error esse sit quis unde inventore similique itaque excepturi amet sequi tenetur illum et dignissimos. Consequatur eius quas, minus aliquam ipsam laboriosam quo ullam illum doloribus vitae itaque et totam odit deleniti velit libero eum. Autem iusto hic consequuntur saepe id corporis, laborum quia magnam perferendis quaerat error cumque amet, voluptatibus inventore perspiciatis quo pariatur! Obcaecati incidunt, aut aliquid in quaerat delectus eveniet assumenda cumque itaque beatae quisquam iure autem qui est quod maiores deserunt nihil corporis laudantium omnis suscipit dicta, fuga ipsam dolor. Maxime unde architecto officia dolorem ratione illo saepe, totam sit dolorum rerum dicta recusandae doloremque quis sapiente repellat rem voluptatem. Excepturi sequi totam dicta minima ut sapiente magnam, officiis blanditiis? Autem laudantium ea ipsa veritatis est, animi esse deserunt reprehenderit itaque. Soluta minus eos delectus dolor ut nam nesciunt! Ad, porro.</div>
      </div>
    </section>

    <script src="js/main.js"></script>
  </body>
</html>


Ключевые моменты CSS

Мобильная навигация .mobile будет фиксированным блоком, изначально скрытым за правую часть браузера с помощью transform: translateX(100%);

При добалении активного класса mobile_active изменяем значение свойства transform: translateX(0);

Для плавного появления и исчезания блока мобильной навигации используем transition: transform 0.4s;


Затемнение .overlay также фиксированный блок, но с изначальной шириной width: 0; чтобы он не перекрывал контент. При добавлении активного класса overlay_active изменяем ширину на width: 100%;.

Для плавного исчезания блока затемнения используем transition: opacity 0.4s, width 0s 0.4s; вместе со свойством opacity

Для плавного появления блока затемнения используем transition: opacity 0.4s, width 0s 0s; в активном классе overlay_active


При ширине окна браузера меньше 768px используем @media запрос @media (max-width: 767.98px), скрываем десктоп навигацию, и отображаем “гамбургер” иконку

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.content {
  padding: 30px 0;
}
.header {
  position: -webkit-sticky;
  position: sticky;
  background: #7371fc;
  color: #fff;
  top: 0;
  padding: 15px 0;
  z-index: 9997;
}
.header__flex {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.header__logo a {
  border-radius: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  height: 66px;
  width: 66px;
  line-height: 1;
  color: #7371fc;
  text-transform: uppercase;
  font-weight: bold;
  text-decoration: none;
  box-shadow: -4px 0 8px rgba(17,17,17,0);
  transition: 0.4s;
}
.header__logo a:hover {
  box-shadow: -4px 0 8px rgba(17,17,17,0.2);
}
.header__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
  display: flex;
}
.header__nav ul li:not(:last-child) {
  margin-right: 15px;
}
.header__nav ul li a {
  color: #fff;
  font-size: 18px;
  text-decoration: none;
}
.header__hamburger {
  display: none;
  height: 24px;
}
.hamburger {
  height: 24px;
  padding: 0;
}
.hamburger:focus {
  outline: none;
}
.hamburger-inner,
.hamburger-inner::after,
.hamburger-inner::before,
.hamburger.is-active .hamburger-inner,
.hamburger.is-active .hamburger-inner::after,
.hamburger.is-active .hamburger-inner::before {
  background: #fff; /* устанавливаем белый цвет "гамбургер" иконки */
}
.mobile {
  background: #7371fc;
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  z-index: 9999;
  padding: 30px;
  width: 290px;
  max-width: 100%;
  box-shadow: -4px 0 8px rgba(17,17,17,0);
  transition: transform 0.4s;
  transform: translateX(100%);
}
.mobile_active {
  box-shadow: -4px 0 8px rgba(17,17,17,0.2);
  transform: translateX(0);
}
.mobile__hamburger {
  position: absolute;
  top: 30px;
  right: 30px;
}
.mobile__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
}
.mobile__nav ul li:not(:last-child) {
  margin-bottom: 15px;
}
.mobile__nav ul li a {
  color: #fff;
  font-size: 18px;
  text-decoration: none;
}
.overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  bottom: 0;
  background: rgba(115,113,252,0.8);
  z-index: 9998;
  transition: opacity 0.4s, width 0s 0.4s;
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  opacity: 0;
}
.overlay_active {
  opacity: 1;
  width: 100%;
  transition: opacity 0.4s, width 0s 0s;
}
@media (max-width: 767.98px) {
  .header__nav {
    display: none;
  }
  .header__hamburger {
    display: block;
  }
}


Пишем Javascript код

Сначала убедимся что структура страницы загружена, объявим функцию mobile, которая будет содержать всю логику мобильной навигации и ниже вызовем функцию mobile для просмотра результата

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодейтсвию

  const mobile = () => { // объявляем основную функцию, чтобы вся логика адаптивной навигации находилась в одном месте

  }

  mobile() // вызываем основную функцию


})

Затем находим элементы управления и блок мобильной навигации - записываем их в константы

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

document.addEventListener('DOMContentLoaded', () => {

  const mobile = () => {

    const hamburgerOpen = document.querySelector('.header__hamburger_open') // находим "гамбургер" иконку для открытия мобильной навигации
    const hamburgerClose = document.querySelector('.mobile__hamburger_close') // находим "гамбургер" иконку для закрытия мобильной навигации
    const mobile = document.querySelector('.mobile') // находим мобильную навигацию

    hamburgerOpen.addEventListener('click', () => { // при клике на "гамбургер" для открытия в шапке
      mobile.classList.add('mobile_active') // отображаем мобильную навигацию добаляя активный класс mobile_active
    })

    hamburgerClose.addEventListener('click', () => { // при клике на "гамбургер" для закрытия, который в блоке мобильной навигации
      mobile.classList.remove('mobile_active') // скрываем мобильную навигацию удаляя активный класс mobile_active
    })
  }

  mobile()

})

Простая реализация мобильной навигации готова.


Добавим дополнительную логику и возможности

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

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

document.addEventListener('DOMContentLoaded', () => {

  const mobile = () => {

    const hamburgerOpen = document.querySelector('.header__hamburger_open')
    const hamburgerClose = document.querySelector('.mobile__hamburger_close')
    const mobile = document.querySelector('.mobile')

    hamburgerOpen.addEventListener('click', () => {
      mobile.classList.add('mobile_active')
    })

    hamburgerClose.addEventListener('click', () => {
      mobile.classList.remove('mobile_active')
    })

    window.addEventListener('resize', () => { // при изменении размеров окна браузера
      if (window.innerWidth >= 768 ) { // если ширина браузера больше или равно 768px
        mobile.classList.remove('mobile_active') // то удаляем активный класс у мобильной навигации
      }
    })

  }

  mobile()
})

Теперь при ширине браузера больше или равной 768px мобильная навигация автоматически скрывается


Добавим небольшое затемнение контента при открытии мобильной навигации. Также добавим возможность закрывать мобильную навигацию при клике на затемнённую область

document.addEventListener('DOMContentLoaded', () => {

  const mobile = () => {


    const hamburgerOpen = document.querySelector('.header__hamburger_open')
    const hamburgerClose = document.querySelector('.mobile__hamburger_close')
    const mobile = document.querySelector('.mobile')
    const overlay = document.querySelector('.overlay') // находим элемент затемнения контента


    hamburgerOpen.addEventListener('click', () => {
      mobile.classList.add('mobile_active')
      overlay.classList.add('overlay_active') // когда отображаем мобильную навигацию, также добавляем активный класс для элемента затемнения
    })

    hamburgerClose.addEventListener('click', () => {
      mobile.classList.remove('mobile_active')
      overlay.classList.remove('overlay_active') // когда скрываем мобильную навигацию, также удаляем активный класс для элемента затемнения
    })

    window.addEventListener('resize', () => {
      if (window.innerWidth >= 768 ) {
        mobile.classList.remove('mobile_active')
        overlay.classList.remove('overlay_active') // удаляем активный класс для элемента затемнения при ширине больше 768px, как и у мобльной навигации
      }
    })

    overlay.addEventListener('click', () => { // при клике на элемент затемнения
      mobile.classList.remove('mobile_active') // удаляем активный класс у мобильной навигации
      overlay.classList.remove('overlay_active') // удаляем активный класс у элемента затемнения
    })

  }

  mobile()

})

Теперь мобильную навигацию можно закрывать при клике на затемнённую область

Немного оптимизируем код - поместим повторяющиеся части кода в отдельные функции

document.addEventListener('DOMContentLoaded', () => {

  const mobile = () => {

    const hamburgerOpen = document.querySelector('.header__hamburger_open')
    const hamburgerClose = document.querySelector('.mobile__hamburger_close')
    const mobile = document.querySelector('.mobile')
    const overlay = document.querySelector('.overlay')

    const openMobile = () => { // объявим функцию открытия мобильной навигации
      mobile.classList.add('mobile_active')
      overlay.classList.add('overlay_active')
    }

    const closeMobile = () => { // объявим функцию закрытия мобильной навигации
      mobile.classList.remove('mobile_active')
      overlay.classList.remove('overlay_active')
    }

    hamburgerOpen.addEventListener('click', () => {
      openMobile() // вызываем функцию открытия мобильной навигации
    })

    hamburgerClose.addEventListener('click', () => {
      closeMobile() // вызываем функцию закрытия мобильной навигации
    })

    window.addEventListener('resize', () => {
      if (window.innerWidth >= 768 ) {
        closeMobile() // вызываем функцию закрытия мобильной навигации
      }
    })

    overlay.addEventListener('click', () => {
      closeMobile() // вызываем функцию закрытия мобильной навигации
    })

  }

  mobile()

})

Посмотреть на Codepen

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



Альтернативная мобильная навигация

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

В данном примере мобильная навигация располагается внутри <header>.

Также используем CSS библиотеку Hamburgers

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <title></title>
    <link rel="stylesheet" href="css/bootstrap-reboot.min.css"/>
    <link rel="stylesheet" href="css/hamburgers.min.css"/> // подключаем CSS библиотеку Hamburgers
    <link rel="stylesheet" href="css/main.css"/>
  </head>
  <body>

    <header class="header">

      <div class="container">
        <div class="header__flex">
          <div class="header__logo"><a href="">Logo</a></div>
          <nav class="header__nav">
            <ul>
              <li><a href="">Home</a></li>
              <li><a href="">About</a></li>
              <li><a href="">Portfolio</a></li>
              <li><a href="">Contact</a></li>
            </ul>
          </nav>
          <div class="header__hamburger">

            <!-- "гамбургер" иконка -->
            <button class="hamburger hamburger--slider-r" type="button">
              <span class="hamburger-box">
                <span class="hamburger-inner"></span>
              </span>
            </button>

          </div>
        </div>
      </div>

      <div class="header__mobile"> <!-- мобильная навигация -->
        <div class="container">
          <ul>
            <li><a href="">Home</a></li>
            <li><a href="">About</a></li>
            <li><a href="">Portfolio</a></li>
            <li><a href="">Contact</a></li>
          </ul>
        </div>
      </div>

    </header>

    <section class="content">
      <div class="container">
        <div class="content__text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe maxime in, quas natus porro? Porro eligendi molestiae quam iure, enim architecto cum dicta assumenda provident sint nobis debitis accusamus consequuntur doloremque consectetur! Modi quae maiores quod culpa! At mollitia, consequatur iste officia laborum beatae accusantium temporibus veniam fugiat esse maxime enim quo voluptate dolorem possimus accusamus quibusdam incidunt, architecto doloremque, autem iure corrupti quaerat excepturi hic. Beatae totam reiciendis accusantium ut aspernatur deserunt, tempore sit repellendus commodi assumenda pariatur. Amet voluptates at vitae molestias exercitationem, obcaecati suscipit id mollitia corrupti deleniti a earum quae dicta laboriosam esse sequi culpa eveniet adipisci repudiandae quisquam architecto ratione. Tempore consectetur dolorum labore quas dolores facilis sint minus illo. Adipisci distinctio accusamus animi magni autem alias, maiores blanditiis, error cumque explicabo veritatis praesentium recusandae placeat? Excepturi, distinctio, velit? Impedit ducimus fuga quisquam veniam quam nihil odit, similique, facere optio voluptates hic vel, iusto doloribus. Quia reiciendis, corrupti, culpa consequuntur consequatur qui, natus earum sint maiores fugiat labore ipsam eum! Est enim aliquid earum non eius, laboriosam quidem? Nulla, nam quis perspiciatis explicabo molestias cumque itaque odio laborum id veniam labore nihil, similique rem velit eum deserunt quaerat, inventore aspernatur sit accusamus nesciunt assumenda architecto neque dolorem eveniet. Explicabo dolore itaque, minima vitae, consectetur assumenda nostrum suscipit, iste distinctio ipsum, dolor fugit. Illum mollitia sequi aut dignissimos! Enim voluptas voluptatum, ullam, architecto a ipsa numquam aspernatur obcaecati voluptate quae est ad quaerat deserunt incidunt dolore molestias dolorem minus soluta, eius magni ex? Optio voluptatem, quam omnis harum ad maiores deleniti autem quia rerum tempora enim voluptas ut! Cupiditate voluptatum, nulla assumenda fugiat perferendis in amet, vero minima quod at hic magni doloremque. Neque atque, quo necessitatibus nostrum, doloribus temporibus. Laborum odit deserunt dignissimos soluta quae vel maiores doloribus accusantium asperiores quisquam, blanditiis non nemo voluptate natus fugit eius impedit adipisci dolore mollitia voluptatum. Aliquid molestiae molestias asperiores non ducimus fugiat in inventore. In deserunt, expedita. Consequuntur perspiciatis quisquam, saepe harum eum nihil sequi facere doloribus sunt. Inventore ipsum officia dolore reprehenderit deleniti repellendus animi, molestiae rerum quo, similique amet ex blanditiis itaque rem, nemo ullam. Numquam rerum vitae nisi officiis maiores cupiditate incidunt quia nam qui cum, dolorem exercitationem hic voluptatem, fugiat quaerat. Quidem, a labore quibusdam quaerat eum earum rem eius consequuntur sapiente impedit eaque nostrum sit consectetur dignissimos iste dolores nisi omnis quasi repudiandae deserunt quam molestias, reprehenderit eos. Qui consectetur voluptatum, consequatur eligendi autem odit optio enim mollitia nesciunt veniam voluptatem, laboriosam. Placeat illo, magnam minus. Nesciunt quis minima doloremque quo, aliquid, vel aspernatur quasi quisquam atque? Atque illum consequuntur a quis consectetur quos fugit! Commodi neque tempore similique, vitae repellendus, nisi consequatur qui at delectus corporis distinctio atque earum facere beatae, nihil debitis dicta incidunt et expedita unde explicabo quasi veniam cumque. A dicta error eum odit est repellendus sit necessitatibus veritatis enim, maiores aliquid amet quidem excepturi id placeat sapiente fugit similique fuga aperiam tenetur optio vero, cumque debitis quia? Fugit reiciendis, facere ad. Et nisi sed molestias, recusandae reiciendis omnis iusto nihil? Quis corrupti fugit excepturi illum incidunt minima accusantium vitae. Aliquid voluptatibus aspernatur dicta totam nulla consequuntur eaque consectetur quasi pariatur, dolorum nemo atque amet ullam obcaecati commodi voluptatum quaerat sint odit corporis, cumque reprehenderit dolorem. Hic adipisci voluptatibus molestias eligendi. Unde illo necessitatibus sint repellat. Distinctio accusamus, hic exercitationem quos repudiandae minima dolorum sequi magni impedit obcaecati id eum rem porro dicta! Voluptates repellat est blanditiis eos molestias, explicabo! Dolores ad et voluptas a recusandae sint excepturi ipsa eaque, natus, eveniet sequi pariatur laborum dolorum repellendus ab nobis, autem accusamus maxime tempora quisquam dicta. Repellendus in veniam debitis sint quia expedita, unde hic, nisi ab tempore doloribus iure voluptatem nam dolore dignissimos! Est nostrum optio iste magni, minima rerum nihil. Eos quidem, nisi architecto ad, fugiat ullam tenetur labore voluptatibus consectetur illum voluptatum tempore, sapiente fugit nemo! Consectetur harum quasi, numquam facilis libero tempora, odit ratione suscipit id laudantium nisi maxime? Placeat corporis illo, recusandae obcaecati atque minima voluptatibus consequatur dolor exercitationem odit natus nisi harum repudiandae perferendis consectetur possimus tempore maiores at molestias, minus a. Autem officiis maxime velit possimus inventore facere provident accusamus excepturi, quaerat ea, minima ullam qui dicta quae quasi reiciendis nulla laborum ad aliquid dolorum veritatis. Ipsum praesentium, aspernatur quisquam dolore repellat iste nisi repellendus autem similique consectetur labore vero officia in sunt expedita architecto? Voluptas autem, vero eum! Aperiam voluptatum facilis libero assumenda iure facere earum, ea quis fuga expedita harum vel eos veniam est velit fugit omnis ipsa quos dicta minus dolores natus, quibusdam nihil. Dignissimos animi, cum, quis nemo ab, fugiat maxime laudantium dolores sint obcaecati voluptatibus ea. Fugiat minus explicabo sit natus consequuntur nihil ex dolore doloribus commodi ipsa quis, labore asperiores tempora aspernatur error atque quae animi esse, expedita quod molestiae! Cum maiores minima aut facere totam voluptate architecto voluptatem suscipit eum, odio repellat repudiandae ipsam nostrum sapiente repellendus nam animi aperiam molestiae optio deserunt ut quisquam! Voluptate blanditiis provident saepe laboriosam voluptates necessitatibus, velit ex recusandae eaque earum, nihil at sed, porro quae minus nam atque nesciunt impedit fuga praesentium iste totam natus dignissimos. Similique harum quia, vel fuga, maiores quam deserunt adipisci nostrum explicabo ipsam ducimus! Sapiente a, quidem, beatae error esse sit quis unde inventore similique itaque excepturi amet sequi tenetur illum et dignissimos. Consequatur eius quas, minus aliquam ipsam laboriosam quo ullam illum doloribus vitae itaque et totam odit deleniti velit libero eum. Autem iusto hic consequuntur saepe id corporis, laborum quia magnam perferendis quaerat error cumque amet, voluptatibus inventore perspiciatis quo pariatur! Obcaecati incidunt, aut aliquid in quaerat delectus eveniet assumenda cumque itaque beatae quisquam iure autem qui est quod maiores deserunt nihil corporis laudantium omnis suscipit dicta, fuga ipsam dolor. Maxime unde architecto officia dolorem ratione illo saepe, totam sit dolorum rerum dicta recusandae doloremque quis sapiente repellat rem voluptatem. Excepturi sequi totam dicta minima ut sapiente magnam, officiis blanditiis? Autem laudantium ea ipsa veritatis est, animi esse deserunt reprehenderit itaque. Soluta minus eos delectus dolor ut nam nesciunt! Ad, porro.</div>
      </div>
    </section>

    <script src="js/main.js"></script>
  </body>
</html>


Изначально скрываем мобильную навигацию при помощи transform: scaleY(0);, при активном классе назначаем transform: scaleY(1);

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 30px;
}
.content {
  padding: 30px 0;
}
.header {
  position: -webkit-sticky;
  position: sticky;
  background: #7371fc;
  color: #fff;
  top: 0;
  padding: 15px 0;
  z-index: 9997;
}
.header__flex {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.header__logo a {
  border-radius: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  height: 66px;
  width: 66px;
  line-height: 1;
  color: #7371fc;
  text-transform: uppercase;
  font-weight: bold;
  text-decoration: none;
  box-shadow: -4px 0 8px rgba(17,17,17,0);
  transition: 0.4s;
}
.header__logo a:hover {
  box-shadow: -4px 0 8px rgba(17,17,17,0.2);
}
.header__nav ul {
  padding: 0;
  margin: 0;
  list-style: none;
  display: flex;
}
.header__nav ul li:not(:last-child) {
  margin-right: 15px;
}
.header__nav ul li a {
  color: #fff;
  font-size: 18px;
  text-decoration: none;
}
.header__hamburger {
  display: none;
  height: 24px;
}
.header__mobile {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: #7371fc;
  transform-origin: center top;
  transform: scaleY(0);
  transition: 0.4s transform;
  overflow-y: auto; /* при большом количестве ссылок в мобильной навигации повляется scrollbar */
}
.header__mobile ul {
  padding: 30px 0;
  margin: 0;
  list-style: none;
}
.header__mobile ul li:not(:last-child) {
  margin-bottom: 10px;
}
.header__mobile ul li a {
  color: #fff;
  text-decoration: none;
  font-size: 18px;
}
.header__mobile_active {
  transform: scaleY(1);
}
.hamburger {
  height: 24px;
  padding: 0;
}
.hamburger:focus {
  outline: none;
}
.hamburger-inner,
.hamburger-inner::after,
.hamburger-inner::before,
.hamburger.is-active .hamburger-inner,
.hamburger.is-active .hamburger-inner::after,
.hamburger.is-active .hamburger-inner::before {
  background: #fff;
}
@media (max-width: 767.98px) {
  .header__nav {
    display: none;
  }
  .header__hamburger {
    display: block;
  }
}


При клике на “гамбургер” иконку проверяем, активна ли мобильная навигация - если не активна, то добавляем активные классы для неё и для “гамбургер” иконки, если активно, то удаляем активные классы

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

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

Подробнее в комментариях ниже

document.addEventListener('DOMContentLoaded', () => { // Структура страницы загружена и готова к взаимодейтсвию

  const mobile = () => { // объявляем основную функцию, чтобы вся логика адаптивной навигации находилась в одном месте

    const hamburger = document.querySelector('.hamburger') // находим "гамбургер" иконку
    const mobile = document.querySelector('.header__mobile') // находим блок мобильной навигации

    const mobileHeight = () => { // объявляем функцию для расчета и установки максимальной высоты
      if (document.documentElement.clientHeight - document.querySelector('.header').offsetHeight < mobile.scrollHeight) { // если разница высоты окна и высоты шапки меньше полной высоты мобильной навигации
        mobile.style.maxHeight = `${document.documentElement.clientHeight - document.querySelector('.header').offsetHeight}px` // устанавливаем максимальную высоту из расчета - разница высоты окна браузера и высоты шапки
      } else {
        mobile.style.maxHeight = '' // иначе сбрасываем инлайн свойство максимальной высоты
      }
    }

    const openMobile = () => { // функция отвечающая за открытие мобильной навигации
      mobile.classList.add('header__mobile_active') // добавляем мобильной навигации активный класс
      hamburger.classList.add('is-active') // добавляем "гамбургер" иконке активный класс
      mobileHeight() // вызываем функцию mobileHeight при открытии мобильной навигации
    }

    const closeMobile = () => { // функция отвечающая за закрытие мобильной навигации
      mobile.classList.remove('header__mobile_active') // удаляем мобильной навигации активный класс
      hamburger.classList.remove('is-active') // удаляем "гамбургер" иконке активный класс
    }

    hamburger.addEventListener('click', () => { // при клике на "гамбургер" иконку
      if (!mobile.classList.contains('header__mobile_active')) { // если мобильная навигация не содержит активный класс
        openMobile() // вызываем функцию открытия мобильной навигации
      } else {
        closeMobile() // вызываем функцию закрытия мобильной навигации
      }
    })

    window.addEventListener('resize', () => { // при изменении размеров окна браузера
      if (window.innerWidth >= 768 ) { // если ширина окна браузера больше или равна 768px
        closeMobile() // вызываем функцию закрытия мобильной навигации
      }
      mobileHeight() // вызываем функцию mobileHeight при изменении размеров окна браузера
    })

    window.addEventListener('click', e => { // при клике в любую точку области окна браузера
      const target = e.target // отслеживаем элемент, на котором был клик
      if (!target.closest('.header')) { // если у элемента, на котором был клик, нет родительского элемента .header
        closeMobile() // то вызываем функцию закрытия мобильной навигации
      }
    })

  }

  mobile() // вызываем основную функцию

})

Посмотреть на Codepen



Объяснение дублирования навигации

Почему мы выносим мобильную навигацию в отдельный блок, а не адаптируем десктоп навигацию?

В реальных макетах шапка сайта часто довольно плотно заполнена различными информационными и навигационными блоками - логотип, слоган, адреса, телефоны, иконки социальных сетей, ссылки навигации и так далее.

При адаптивной вёрстке также появляется необходимость эти блоки скрывать, но тем не менее, они должны остаться доступны

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

На счет дублирования - семантически основной навигацией сайта будут являться ссылки в десктоп навигации в теге <nav></nav>, который будет учитываться поисковиками как навигационные ссылки по сайту.

Ссылки в мобильной навигации второстепенны и их просто оборачиванием в <div></div>

С таким подходом мы получаем намного более гибкую, логичную и простую в реализации адаптивную навигацию



Итоги

В этой статье постарался охватить большинство нюансов адаптивной навигации.

Дополнительно к этому рассмотрели некоторые полезные JavaScript решения, которые пригодятся в будущем