Создание отзывчивой горизонтальной навигации типа "Приоритет+"

  • CSS
  • 210
  • 0
  • 0
  • 0
  • 13 дней назад

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

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

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

263c11d9099b2cb73105cbec0fd8.png

Разметка 

Первым шагом будет создание HTML-разметки для нашей навигации "Приоритет+".

<nav class="nav">
  <ul class="nav__list">
    <li class="nav__item">
      <a class="nav__link is-active" href="#dashboard">Dashboard</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#lorem">Lorem</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#ipsum">Ipsum</a>
    </li>
    <li class="nav__item">
      <a class="nav__link" href="#dolor">Dolor</a>
    </li>
  </ul>
  <div class="nav__shadow nav__shadow--start"></div>
  <div class="nav__shadow nav__shadow--end"></div>
</nav>

Как видите, мы используем очень простой подход к разметке навигации. Использование тега <nav> в качестве "обёртки" для навигации даёт ей правильное семантическое значение. Хотя использование <ul> для создания навигаций не является строго необходимым, оно стало более или менее стандартным способом ее разметки.

(S)CSS 

 Мы используем Sass и методологию BEM для дизайна.

$nav-height: 3em;
$nav-scrollbar-height: 1.5em;
$nav-shadow-width: 4.5em;
.nav {
  position: relative;
  height: $nav-height;
  display: flex;
  align-items: center;
  @media (pointer: coarse) {
    // Скрыть полосу прокрутки на устройствах с сенсорным вводом.
    overflow: hidden;
  }
}
.nav__list {
  position: relative;
  display: flex;
  margin-left: 1em;
  margin-right: 1em;
  padding: 0;
  align-items: center;
  list-style-type: none;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  @media (pointer: coarse) {
    // Переместить полосу прокрутки за область видимости, 
    // делая элемент выше, чем родительский элемент
    height: $nav-height + $nav-scrollbar-height;
  }
}
.nav__item {
  flex-shrink: 0;
  
  &:not(:first-child) {
    margin-left: 1em;
  }
}
.nav__link {
  display: inline-flex;
  height: $nav-height;
  align-items: center;
  text-decoration: none;
  
  &.is-active {
    font-weight: bold;
  }
}
.nav__shadow {
  width: $nav-shadow-width;
  height: $nav-height;
  position: absolute;
  top: 0;
  // Используем 0% значение RGBA вместо прозрачного из-за Safari.
  background: linear-gradient(to right, rgba(#fff, 0), #fff 80%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.1s;
  
  &.is-visible {
    opacity: 1;
  }
}
.nav__shadow--start {
  left: 0;
  transform: rotate(180deg);
}
.nav__shadow--end {
  right: 0;
}

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

  • $nav-height: Поскольку позже мы захотим скрыть горизонтальную полосу прокрутки, появляющуюся, если навигация не умещается на экран, мы должны установить для навигации определенную высоту. 
  • $nav-scrollbar-height:Эта переменная в основном определяет пространство по вертикали, которое может быть занято горизонтальной полосой прокрутки. Не обязательно, чтобы она точно соответствовала высоте полосы, достаточно, чтобы она была как минимум высотой с полосу или выше.
  • $nav-shadow-width:Чтобы сделать очевидным для пользователя факт наличия скрытых элементов навигации, мы используем тени, которые покажут достаточно текста из следующего элемента, из чего будет понятно, что дальше есть еще что-то. Данная переменная определяет ширину теней.

Хотя горизонтальная прокрутка довольно удобная на сенсорных устройствах, она может быть очень обременительной (или невыносимой, если спрятать полосу) на устройствах без сенсорного ввода. Поэтому мы используем pointer: coarse для скрытия полосы прокрутки только на устройствах с сенсорным вводом. Имейте в виду, что показ полосы может выглядеть некрасиво, но, если ограничить количество элементов навигации до разумного количества, это должно будет происходить очень редко, потому что только некоторое количество устройств с очень маленькими экранами не поддерживают сенсорный ввод.

-webkit-overflow-scrolling: touch; Свойства CSS позволяют сделать прокрутку при нажатии пальцем на iOS устройствах. Такая прокрутка установлена по умолчанию в качестве вертикальной на iOS; включение ее и в качестве горизонтальной делает ее "правильной" по ощущению.

Установка pointer-events: none;на темных элементах рекомендована, потому что в противном случае пользователь может нажать на элемент навигации, скрытый тенью, вследствие чего нажатие достигает не сам элемент, а тень, что , весьма вероятно, разочарует пользователя.

JavaScript 

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

const $navList = document.querySelector('.nav__list');
const $shadowStart = document.querySelector('.nav__shadow--start');
const $shadowEnd = document.querySelector('.nav__shadow--end');
function handleShadowVisibility() {
  const maxScrollStartReached = $navList.scrollLeft <= 0;
  const maxScrollEndReached = $navList.scrollLeft >= $navList.scrollWidth - $navList.offsetWidth;
  toggleShadow($shadowStart, maxScrollStartReached);
  toggleShadow($shadowEnd, maxScrollEndReached);
}
function toggleShadow($el, maxScrollReached) {
  const shadowIsVisible = $el.classList.contains('is-visible');
  const showShadow = !maxScrollReached && !shadowIsVisible;
  const hideShadow = maxScrollReached && shadowIsVisible;
  // Используем requestAnimationFrame для оптимального исполнения прокрутки.
  // https://stackoverflow.com/a/44779316
  if (showShadow) {
    window.requestAnimationFrame(() => $el.classList.add('is-visible'));
  } else if (hideShadow) {
    window.requestAnimationFrame(() => $el.classList.remove('is-visible'));
  }
}
handleShadowVisibility();
$navList.addEventListener('scroll', (e) => handleShadowVisibility(e));

handleShadowVisibility() определяет, должны ли с учётом текущей позиции прокрутки горизонтальной навигации показываться тени, и, если да, то какие. Если элементы скрыты справа, показывается нужная тень. Если пользователь начинает прокручивать вправо, тень слева мгновенно показывается. Как только он достигнет правого конца навигации, тень справа исчезает.

Внутри функции toggleShadow() мы определяем, должен затемненный элемент быть скрыт или показан. Поскольку прокрутка может стать медленной, если перекрашивание макета инициируется во время нее, мы используем requestAnimationFrame для уменьшения перегрузки макета.

Демо

Ссылка на демо в codepen