Простой слайдер для нескольких пунктов

Разметка

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

Код
<div id="mi-slider" class="mi-slider">
  <ul>
  <li><a href="#"><img src="images/1.jpg" alt="img01"><h4>Ботинки</h4></a></li>
  <li><a href="#"><img src="images/2.jpg" alt="img02"><h4>Классика</h4></a></li>
  <li><a href="#"><img src="images/3.jpg" alt="img03"><h4>Мокасины</h4></a></li>
  <li><a href="#"><img src="images/4.jpg" alt="img04"><h4>Кроссовки</h4></a></li>
  </ul>
  <ul>
  <li><a href="#"><img src="images/5.jpg" alt="img05"><h4>Ремни</h4></a></li>
  <li><a href="#"><img src="images/6.jpg" alt="img06"><h4>Шляпы и кепи</h4></a></li>
  <li><a href="#"><img src="images/7.jpg" alt="img07"><h4>Очки</h4></a></li>
  <li><a href="#"><img src="images/8.jpg" alt="img08"><h4>Шарфы</h4></a></li>
  </ul>
  <ul>
  <li><a href="#"><img src="images/9.jpg" alt="img09"><h4>Свобода</h4></a></li>
  <li><a href="#"><img src="images/10.jpg" alt="img10"><h4>Роскошь</h4></a></li>
  <li><a href="#"><img src="images/11.jpg" alt="img11"><h4>Спорт</h4></a></li>
  </ul>
  <ul>
  <li><a href="#"><img src="images/12.jpg" alt="img12"><h4>Чемоданы</h4></a></li>
  <li><a href="#"><img src="images/13.jpg" alt="img13"><h4>Дорожные сумки</h4></a></li>
  <li><a href="#"><img src="images/14.jpg" alt="img14"><h4>Для ноутбуков</h4></a></li>
  <li><a href="#"><img src="images/15.jpg" alt="img15"><h4>Портфели</h4></a></li>
  </ul>
  <nav>
  <a href="#">Обувь</a>
  <a href="#">Аксессуары</a>
  <a href="#">Часы</a>
  <a href="#">Сумки</a>
  </nav>
</div>


CSS

Примечание: префиксы производителей браузеров опущены для большей наглядности кода.

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

Сначала определим стили для контейнера, который будет иметь класс mi-slider. У него будет предустановлена высота, которая требуется для установки правильного положения элементов ul:

Код

.mi-slider {
  position: relative;
  margin-top: 30px;
  height: 490px;
}


Элемент ul будет позиционироваться абсолютно, чтобы списки располагались один над другим. Мы будем перемещать пункты списка но не сам список. Установим для свойства pointer-events значение none, так как нам нужно чтобы на нажатие кнопки мыши реагировала ссылка текущего списка:

Код
.mi-slider ul {
  list-style-type: none;
  position: absolute;
  width: 100%;
  left: 0;
  bottom: 140px;
  overflow: hidden;
  text-align: center;
  pointer-events: none;
}


Свойство pointer-events для текущего списка нужно обновить, чтобы ссылки в содержании стали доступны для нажатия:

Код
.mi-slider ul.mi-current {
  pointer-events: auto;
}


Когда JavaScript отключен мы сохраняем внешний вид (используем Modernizr):

Код

.no-js .mi-slider ul {
  position: relative;
  left: auto;
  bottom: auto;
  margin: 0;
  overflow: visible;
}


Для того, чтобы центрировать все пункты установим для свойство выравнивания текста по центру для элемента ul, а для пунктов списка свойство display:inline-block; и ширину 20%. Такое значение для ширины даст гарантию, что пункт поместится в списка и сохранит подвижность.

По умолчанию все пункты смещаются вправо. Используем значение 600%, так как его будет достаточно, чтобы убрать все из поля видимости. Также добавляем небольшую трансформацию для прозрачности:

Код
.mi-slider ul li {
  display: inline-block;
  padding: 20px;
  width: 20%;
  max-width: 300px;
  transform: translateX(600%);
  transition: opacity 0.2s linear;
}


Без JavaScript смещения не требуется:

Код
.no-js .mi-slider ul li {
  transform: translateX(0);
}


Определим стили для содержимого пунктов списка. Обратите внимание на установку для свойства изображений max-width значения 100%. Таким образом обеспечивается целостность шаблона при масштабировании изображений в соответствии с размерами контейнера, который представляет собой наш элемент li с процентным значением ширины.

Код
.mi-slider ul li a,
.mi-slider ul li img {
  display: block;
  margin: 0 auto;
}
   
.mi-slider ul li a {
  outline: none;
  cursor: pointer;
}
   
.mi-slider ul li img {
  max-width: 100%;
  border: none;
}
   
.mi-slider ul li h4 {
  display: inline-block;
  font-family: Baskerville, "Baskerville Old Face", "Hoefler Text", Garamond, "Times New Roman", serif;
  font-style: italic;
  font-weight: 400;
  font-size: 18px;
  padding: 20px 10px 0;
}


При наведении курсора будем анимировать прозрачность пункта:

Код
.mi-slider ul li:hover {
  opacity: 0.7;
}


Навигация нуждается в установке значения свойства top, так как шаблон позиционируется абсолютно. Мы центрируем навигацию устанавливая автоматические боковые поля и устанавливая максимальную ширину 800px:

Код
.mi-slider nav {
  position: relative;
  top: 400px;
  text-align: center;
  max-width: 800px;
  margin: 0 auto;
  border-top: 5px solid #333;
}


При отключенном JavaScript выводить навигацию совсем не нужно:

Код
.no-js nav {
  display: none;
}


Ссылки навигации будут иметь достаточные отступы и трансформироваться при наведении курсора:

Код
.mi-slider nav a {
  display: inline-block;
  text-transform: uppercase;
  letter-spacing: 5px;
  padding: 40px 30px 30px 34px;
  position: relative;
  color: #888;
  outline: none;
  transition: color 0.2s linear;
}
   
.mi-slider nav a:hover,
.mi-slider nav a.mi-selected {
  color: #000;
}


Класс mi-selected, так же как и класс mi-current для списка, будет устанавливаться с помощью JavaScript.

Добавим небольшую стрелку вверху. Используем псевдо классы :before и :after для создания двух треугольников с помощью рамок

Код
.mi-slider nav a.mi-selected:after,
.mi-slider nav a.mi-selected:before {
  content: '';
  position: absolute;
  top: -5px;
  border: solid transparent;
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}
   
.mi-slider nav a.mi-selected:after {
  border-color: transparent;
  border-top-color: #fff;
  border-width: 20px;
  left: 50%;
  margin-left: -20px;
}
   
.mi-slider nav a.mi-selected:before {
  border-color: transparent;
  border-top-color: #333;
  border-width: 27px;
  left: 50%;
  margin-left: -27px;
}


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

Код
.mi-slider ul:first-child li,
.no-js .mi-slider ul li {
  animation: scaleUp 350ms ease-in-out both;
}
   
@keyframes scaleUp {
  0% { transform: translateX(0) scale(0); }
  100% { transform: translateX(0) scale(1); }
}


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

Код
.mi-slider ul:first-child li:first-child {
  animation-delay: 90ms;
}
   
.mi-slider ul:first-child li:nth-child(2) {
  animation-delay: 180ms;
}
   
.mi-slider ul:first-child li:nth-child(3) {
  animation-delay: 270ms;
}
   
.mi-slider ul:first-child li:nth-child(4) {
  animation-delay: 360ms;
}


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

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

Код
/* Движение справа */

.mi-slider ul.mi-moveFromRight li {
  animation: moveFromRight 350ms ease-in-out both;
}

/* Движение слева */

.mi-slider ul.mi-moveFromLeft li {
  animation: moveFromLeft 350ms ease-in-out both;
}

/* Движение направо */

.mi-slider ul.mi-moveToRight li {
  animation: moveToRight 350ms ease-in-out both;
}

/* Движение налево */

.mi-slider ul.mi-moveToLeft li {
  animation: moveToLeft 350ms ease-in-out both;
}


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

Код
.mi-slider ul.mi-moveToLeft li:first-child,
.mi-slider ul.mi-moveFromRight li:first-child,
.mi-slider ul.mi-moveToRight li:nth-child(4),
.mi-slider ul.mi-moveFromLeft li:nth-child(4) {
  animation-delay: 0ms;
}


Увеличиваем задержки соответственно:

Код
.mi-slider ul.mi-moveToLeft li:nth-child(2),
.mi-slider ul.mi-moveFromRight li:nth-child(2),
.mi-slider ul.mi-moveToRight li:nth-child(3),
.mi-slider ul.mi-moveFromLeft li:nth-child(3) {
  -webkit-animation-delay: 90ms;
  animation-delay: 90ms;
}
   
.mi-slider ul.mi-moveToLeft li:nth-child(3),
.mi-slider ul.mi-moveFromRight li:nth-child(3),
.mi-slider ul.mi-moveToRight li:nth-child(2),
.mi-slider ul.mi-moveFromLeft li:nth-child(2) {
  -webkit-animation-delay: 180ms;
  animation-delay: 180ms;
}
   
.mi-slider ul.mi-moveToLeft li:nth-child(4),
.mi-slider ul.mi-moveFromRight li:nth-child(4),
.mi-slider ul.mi-moveToRight li:first-child,
.mi-slider ul.mi-moveFromLeft li:first-child {
  -webkit-animation-delay: 270ms;
  animation-delay: 270ms;
}


Теперь определим сами анимации. Например, движение справа означает, что мы устанавливаем значение translateX равным 600% и смещаем его в 0. Движение налево означает установку конечной позиции в -600%, чтобы убрать пункт за пределы области видимости. И так далее:

Код
@keyframes moveFromRight {
  0% { transform: translateX(600%); }
  100% { transform: translateX(0); }
}
   
@keyframes moveFromLeft {
  0% { transform: translateX(-600%); }
  100% { transform: translateX(0); }
}
   
@keyframes moveToRight {
  0% { transform: translateX(0%); }
  100% { transform: translateX(600%); }
}
   
@keyframes moveToLeft {
  0% { transform: translateX(0%); }
  100% { transform: translateX(-600%); }
}


И последнее по списку, но не по значимости: воспользуемся медиа запросами для выравнивания содержания слайдера на маленьких экранах.

Начнем с навигации. так как она должна сохраняться целостной в любых условиях:

Код
@media screen and (max-width: 910px){
  .mi-slider nav {
  max-width: 90%;
  }
   
  .mi-slider nav a {
  font-size: 12px;
  padding: 40px 10px 30px 14px;
  }
}


Так как мы установили фиксированную высоту слайдера, то ее нужно адаптировать:

Код
@media screen and (max-width: 740px){
  .mi-slider {
  height: 300px;
  }
   
  .mi-slider nav {
  top: 220px;
  }
}


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

Код
@media screen and (max-width: 490px){  
  .mi-slider {
  text-align: center;
  height: auto;
  }
   
  .mi-slider ul {
  position: relative;
  display: inline;
  bottom: auto;
  pointer-events: auto;
  }
   
  .mi-slider ul li {
  animation: none !important;
  transform: translateX(0) !important;
  padding: 10px 3px;
  min-width: 140px;
  }
   
  .mi-slider nav {
  display: none;
  }
}


Теперь пришло время переходить к jQuery.

JavaScript

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

Начнем с кеширования некоторых элементов и инициализации переменных:

Код
_init : function( options ) {

  // Категории (ul)
  this.$categories = this.$el.children( 'ul' );
  // Навигация
  this.$navcategories = this.$el.find( 'nav > a' );
  var animEndEventNames = {
  'WebkitAnimation' : 'webkitAnimationEnd',
  'OAnimation' : 'oAnimationEnd',
  'msAnimation' : 'MSAnimationEnd',
  'animation' : 'animationend'
  };
  // Название анимации и события
  this.animEndEventName = animEndEventNames[ Modernizr.prefixed( 'animation' ) ];
  // Поддержка анимаций и событий
  this.support = Modernizr.csstransforms && Modernizr.cssanimations;
  // Если анимация проводится
  this.isAnimating = false;
  // Текущая категория
  this.current = 0;
  var $currcat = this.$categories.eq( 0 );
  if( !this.support ) {
  this.$categories.hide();
  $currcat.show();
  }
  else {
  $currcat.addClass( 'mi-current' );
  }
  // Текущая категория навигации
  this.$navcategories.eq( 0 ).addClass( 'mi-selected' );
  // Инициализация событий
  this._initEvents();

},


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

Код
_initEvents : function() {

  var self = this;
  this.$navcategories.on( 'click.catslider', function() {
  self.showCategory( $( this ).index() );
  return false;
  } );

  // Сброс при измении размеров окна
  $( window ).on( 'resize', function() {
  self.$categories.removeClass().eq( 0 ).addClass( 'mi-current' );
  self.$navcategories.eq( self.current ).removeClass( 'mi-selected' ).end().eq( 0 ).addClass( 'mi-selected' );
  self.current = 0;
  } );

  },
  showCategory : function( catidx ) {

  if( catidx === this.current || this.isAnimating ) {
  return false;
  }
  this.isAnimating = true;
  // Обновляем выбранную навигацию
  this.$navcategories.eq( this.current ).removeClass( 'mi-selected' ).end().eq( catidx ).addClass( 'mi-selected' );

  var dir = catidx > this.current ? 'right' : 'left',
  toClass = dir === 'right' ? 'mi-moveToLeft' : 'mi-moveToRight',
  fromClass = dir === 'right' ? 'mi-moveFromRight' : 'mi-moveFromLeft',
  // Текущая категория
  $currcat = this.$categories.eq( this.current ),
  // Новая категория
  $newcat = this.$categories.eq( catidx ),
  $newcatchild = $newcat.children(),
  lastEnter = dir === 'right' ? $newcatchild.length - 1 : 0,
  self = this;

  if( this.support ) {

  $currcat.removeClass().addClass( toClass );
   
  setTimeout( function() {

  $newcat.removeClass().addClass( fromClass );
  $newcatchild.eq( lastEnter ).on( self.animEndEventName, function() {

  $( this ).off( self.animEndEventName );
  $newcat.addClass( 'mi-current' );
  self.current = catidx;
  var $this = $( this );
  // Решение для ошибки в Chrome  
  self.forceRedraw( $this.get(0) );
  self.isAnimating = false;

  } );

  }, $newcatchild.length * 90 );

  }
  else {

  $currcat.hide();
  $newcat.show();
  this.current = catidx;
  this.isAnimating = false;

  }

  },
  // На основании http://stackoverflow.com/a/8840703/989439
  forceRedraw : function(element) {
  if (!element) { return; }
  var n = document.createTextNode(' '),
  position = element.style.position;
  element.appendChild(n);
  element.style.position = 'relative';
  setTimeout(function(){
  element.style.position = position;
  n.parentNode.removeChild(n);
  }, 25);
  }

}

$.fn.catslider = function( options ) {
  var instance = $.data( this, 'catslider' );
  if ( typeof options === 'string' ) {
  var args = Array.prototype.slice.call( arguments, 1 );
  this.each(function() {
  instance[ options ].apply( instance, args );
  });
  }
  else {
  this.each(function() {
  instance ? instance._init() : instance = $.data( this, 'catslider', new $.CatSlider( options, this ) );
  });
  }
  return instance;
};


Готово!

  • FalleN

  • 6527

  • 1

  • 217
Теги: animation, css3, jQuery

Ссылки на статью:

Похожие статьи: