Анимированный индикатор активного пункта меню на CSS

Анимированный индикатор активного пункта меню на CSS

От автора: в этой статье по созданию индикатора для меню на чистом CSS я расскажу про необычные способы применения смежных селекторов и псевдо-классов. Вы можете подумать, что тут не обошлось без JavaScript. На самом деле тут нет никакого JS, а просто по-умному использованы возможности CSS. С другой стороны, проще было бы сделать это с помощью JS.

Ниже представлен результат:

Процесс создания разобьем в три этапа:

Основная структура и стили

Создание индикатора

Заставляем индикатор двигаться

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

Шаг 1: Основная структура и стили

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

<ul class="PrimaryNav">
  <li class="Nav-item">Home</li>
  <li class="Nav-item">About</li>
  <li class="Nav-item is-active">Writing</li>
  <li class="Nav-item">Clients</li>
  <li class="Nav-item">Contact</li>
</ul>

Не так и мудрено. У нас есть ul с классом PrimaryNav. Данный тег служит контейнером для элементов списка, каждый элемент с классом Nav-item.

Задаем переменные

Одна из ключевых особенностей меню это максимальная ширина, занимаемая элементами меню. Элементы меню растягиваются на всю ширину контейнера. В нашем случае мы зададим переменную $menu-items, которую будем использовать для расчетов ширины каждого элемента .Nav-item. Также мы добавим переменную $indicator-color – угадали – она отвечает за цвет индикатора при наведении на него мыши.

// Переменные пунктов меню
// Количество элементов меню
$menu-items: 5;		
// Умножаем на 1% чтобы перевести в проценты
$width: (100/$menu-items) * 1%;

// Цвета
$background-color: #121212;
$indicator-color: #e82d00;

Теперь, добавим базовые стили меню:

// Родительский контейнер
.PrimaryNav {	
  // Удаляем маркеры
  list-style: none;	
  // Центрируем!
  margin: 50px auto;	
  // nav никогда не превысит эту ширину и рассчитанное нами значение в процентах
  max-width: 720px;
  padding: 0;
  width: 100%;
}

// Пункты меню
.Nav-item {
  background: #fff;
  display: block;
  float: left;
  margin: 0;
  padding: 0;
  text-align: center;
  // По нашим расчетам пунктов 5, а значит ширина будет 20%
  width: $width;		

  // Первый пункт меню
  &:first-child {	
    border-radius: 3px 0 0 3px;
  }

  // Последний пункт меню
  &:last-child {	
    border-radius: 0 3px 3px 0;
  }

  // Если пункт активный, задаем ему цвет индикатора
  &.is-active a {	
    color: $indicator-color;
  }

  a {
    color: $background-color;
    display: block;
    padding-top: 20px;
    padding-bottom: 20px;
    text-decoration: none;

    &:hover {
      color: $indicator-color;
    }
  }
}

Шаг 2: Создание индикатора

Нам необходимо добавить еще один класс. Можно было бы обойтись одним классом .PrimaryNav, но подход с двумя классами более гибкий. У нас уже есть класс .PrimaryNav, в нем содержатся основные стили меню. Создадим класс .width-indicator – это будет наш индикатор:

<ul class="PrimaryNav with-indicator">

</ul>

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

Основная сложность в получении каждого пункта меню для последующей коммуникации между ними. В маркированном списке первый элемент (:first-child) может взаимодействовать со вторым через смежный селектор + или ~. Но второй уже не может общаться с первым (в CSS нельзя ходить по DOM в обратном направлении).

Выходит, что лучше всего работать с псевдо-классом :last-child. Данный псевдо-класс работает со всеми состояниями соседних элементов, как :hover, так и :active. Поэтому это идеальный кандидат для установки индикатора. Индикатор создается с помощью :before и :after в псевдо-классе :last-child. Элемент :before это CSS треугольник, центрированный отрицательным значением margin’а.

// Индикатор наведения мыши
.with-indicator {
  // Контейнер меню задается относительным, а псевдо-класс last-child задается абсолютно.
  position: relative;

.Nav-item:last-child {
  &:before, &:after {
    content: '';
    display: block;
    position: absolute;
  }
  
  // CSS Треугольник
  &:before {
    width: 0;
    height: 0;
    border: 6px solid transparent;
    border-top-color: $color-indicator;
    top: 0;
    left: 12.5%;
    // Фиксим смещение – может немного изменяться
    margin-left: -3px;
  }

  // блок за текстом
  &:after {
    width: $width;
    background: $indicator-color;
    top: -6px;
    bottom: -6px;
    left: 0;
    z-index: -1;
  }
}
}

Шаг 3: Заставляем индикатор двигаться

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

На данный момент списку ul задано позиционирование position: relative по умолчанию. Это означает, что наш индикатор установлен на первый пункт меню. Передвигать индикатор можно, изменяя значение left. Так как все пункты одинаковой ширины, то один сдвиг для псевдо-классов :before и :after в :last-child должен быть равен ширине .Nav-item. Помните нашу переменную $widths? Ее можно использовать для атрибута left. Вот так это будет выглядеть в vanilla CSS:

.with-indicator .Nav-item:nth-child(1).is-active ~ .Nav-item:last-child:after {
  left: 0;
}
.with-indicator .Nav-item:nth-child(2).is-active ~ .Nav-item:last-child:after {
  left: 20%;
}
.with-indicator .Nav-item:nth-child(3).is-active ~ .Nav-item:last-child:after {
  left: 40%;
}
.with-indicator .Nav-item:nth-child(4).is-active:after {
  left: 60%;
}
.with-indicator .Nav-item:nth-child(5).is-active:after {
  left: 80%;
}

Добавим динамики с помощью Sass:

// Переменный пунктов меню
// Количество пунктов меню плюс 1 для сдвига
$menu-items: 5;	
// Настоящее число пунктов меню
$menu-items-loop-offset: $menu-items - 1;
// Умножаем на 1% чтобы перевести в проценты
$width: (100/$menu-items) * 1%;

.with-indicator {
  @for $i from 1 through $menu-items-loop-offset {
    // Когда .Nav-item активен,сдвигаем индикатор к активному пункту.
    .Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:after {
      left:($width*$i)-$width;
    }
    
   .Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:before {
      left:($width*$i)+($width/2)-$width; /* сдвигаем треугольник на активный пункт меню. */
    }	
  } // конец цикла @for

Заметим, что у треугольника :before дополнительный сдвиг на полширины. Добавим ко всему этому анимации и еще один цикл for для определения местоположения индикатора. Метод основан на определении текущей страницы. При наведении мыши на пункт меню, т.е. :hover, индикатор будет двигаться. Но как только вы уберете мышь, состояние элемента снова станет is-active. Красивый и аккуратный способ сделать индикатор меню без JavaScript.

// Когда :last-child is-active или :hover необходимо использовать !important для перезаписи значения
@for $i from 1 through $menu-items-loop-offset {
  // Когда пункт :hover, сдвигаем на него индикатор.
  .Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:after {
    left:($width*$i)-$width !important;
  }

  .Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:before{
    left:($width*$i)+($width/2)-$width !important;
  }
} // конец цикла @for 

// Убедимся, что значения возвращаются на last-child
.Nav-item {
  &:last-child {
    &:hover, &.is-active {
      &:before {
        left: (100%-$width)+($width/2) !important;
      }
      &:after{
        left: 100%-$width !important;
      }
    }        
  }
}

Результат

Вот и все! Анимированный индикатор меню без JavaScript.

Источник: https://css-tricks.com/

Редакция: Команда webformyself.

Практика HTML5 и CSS3 с нуля до результата!

Получите бесплатный пошаговый видеокурс по основам адаптивной верстки с полного нуля на HTML5 и CSS3

Получить

Метки: ,

Комментарии Вконтакте:

Комментарии Facebook:

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

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

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree