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

В данном уроке мы сделаем простой слайдер для пунктов списка на основе анимаций CSS и jQuery. Идея для урока инспирирована слайдером продукции Aplle, где несколько товаров выскальзывают с использованием анимации. Мы трансформируем данную концепцию для онлайн магазина, чтобы представлять товары в разных категориях. Категории с небольшим количеством товаров отлично подходят к данному слайдеру. Конечно, для большого количества товаров такое решение далеко от оптимума, но для магазинов с небольшим ассортиментом слайдер станет отличным инструментом.

Разметка

В качестве разметки 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;
	};

Готово!
Теги:
слайдер
Добавлено: 21 Сентября 2013 02:21:53 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...