[Из песочницы] Ant-карусель на CSS и Javascript

С появлением CSS3 появилась возможность совершать анимацию без использования JS-библиотек, таких, например, как jQuery. CSS3 свойство transition позволяет плавно изменять другие свойства элемента (width, height, margin, opacity и пр.), задав в качестве параметров время и закон трансформации. Предлагаю небольшую по размерам, но достаточно функциональную карусель на чистом Javascript. Небольшую, как муравей, что гораздо меньше чем сова. Но почти с такими же возможностями.

Ant-карусель позволяет:

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


Помещаем нашу карусель в файл index.html (пример файла см. ниже):

HTML



Здесь использованы элементы , но вместо них можно использовать , если вам это удобнее. Стрелки и индикаторные точки располагаются абсолютным позиционированием в соответствующих контейнерах. Для стрелок используются рисунки в виде треугольных скобок, которые, при желании, вы можете заменить своими рисунками или генерацией изображения псевдо-элементами : before и : after.
Создаём карусель с тремя видимыми элементами и шириной элемента 270 пикселей. Тогда максимальная ширина карусели 810 пикселей. Подключаем CSS-файл:

CSS
.ant-carousel {
	max-width: 810px;  /* укажите здесь ваше значение */
	position: relative;
}
.ant-carousel-hider {
	overflow: hidden;
}
.ant-carousel-list {
	width: auto;
	margin: 0;
	padding: 0;
	list-style-type: none;
	display: -webkit-flex;
	display: flex;
	-webkit-justify-content: flex-start;
	justify-content: flex-start;
}
.ant-carousel-element {
	display: block;
	-webkit-flex: 0 0 auto;
	flex: 0 0 auto;
	width: 270px;  /* укажите здесь ваше значение */
	text-align: center;  /* укажите здесь ваше значение */
}
/* Navigation item styles */
div.ant-carousel-arrow-left, div.ant-carousel-arrow-right {
 	width: 22px;
	height: 40px;
	position: absolute;
	cursor: pointer;
	opacity: 0.6;
	z-index: 2;
	display: block;
}
div.ant-carousel-arrow-left {
 	left: -40px;
	top: 40%;
 	background: url("ant-arrow-left.png”)  no-repeat;
}
div.ant-carousel-arrow-right {
 	 right: -40px;
	top: 40%;
 	background: url("ant-arrow-right.png”)  no-repeat;
}
div.ant-carousel-arrow-left: hover {
	opacity: 1.0;
}
div.ant-carousel-arrow-right: hover {
	opacity: 1.0;
}
div.ant-carousel-dots {
	width: 100%;
	 height: auto;
	position: absolute;
	left: 0;
	bottom: -30px;
	z-index: 2;
	text-align: center;
}
span.ant-dot {
	width: 10px;
	height: 10px;
	margin: 5px 7px;
	padding: 0;
	display: inline-block;
	background-color: #BBB;
	-webkit-border-radius: 5px;
	border-radius: 5px;
	cursor: pointer;
}


Располагаем элементы в контейнере ant-carousel-list, устанавливаем для него свойство display: flex и прижимаем все элементы к левому краю justify-content: flex-start. Свойство flex: 0 0 auto устанавливает flex-shrink в 0 (по умолчанию 1). Прокрутка элементов карусели осуществляется при помощи свойства transiton плавным изменением отступа margin-left от нуля до ширины элемента (в одну сторону) или от ширины элемента до нуля (в другую сторону). Для функции трансформации (прокрутки) используется значение ease.

Переходим к программе. В опциях программы можно настраивать:

  • количество видимых элементов;
  • просмотр элементов в виде ленты от первого до последнего или в бесконечном цикле (лента замыкается в кольцо);
  • автоматическая или ручная прокрутка элементов;
  • интервал автоматической прокрутки;
  • скорость анимации;
  • включение/отключение элементов навигации: стрелки, индикаторные точки, перелистывание прикосновением (для тактильных экранов).


Инициализация программы начинается с того, что определяется количество элементов карусели, присваиваются начальные значения внутренним переменным, назначаются обработчики событий на стрелки и точки (если подключены). Если опция автоматической прокрутки подключена, назначаются дополнительные обработчики, которые останавливают прокрутку при наведении мыши на элементы карусели. Прокрутка прикосновением срабатывает, если между точкой касания пальцем экрана и точкой отрыва пальца от экрана больше 20 пикселей и общее время прикосновения пальца к экрану меньше 80 мс. У автора пока нет большого опыта работы с данной каруселью, поэтому, возможно, приведённые значения требуют уточнения. Для более надёжного срабатывания обработчика прокрутки расстояние между точками, возможно, стоит уменьшить до 10 или 15 пикселей, а время прикосновения увеличить до 100 или 120 мс. Пользователь данной карусели может подкорректировать эти значения сам после приобретения некоторого опыта её использования.

Алгоритм прокрутки элементов отличается в зависимости от того, включена опция цикла или нет. Если включена, при прокрутке вправо (функция elemPrev) свойство margin-left всей линейки элементов this.crslList уменьшается от нуля до отрицательного значения, равного ширине элемента elemWidth. Одновременно последний элемент справа клонируется и вставляется перед первым элементом, после чего последний элемент удаляется. Линейке присваивается свойство «transition: margin '+ options.speed+'ms ease», где options.speed — скорость анимации, ease — функция анимации. Теперь можно осуществлять прокрутку. Свойство margin-left плавно меняется от отрицательного значения до нуля, вся линейка плавно смещается вправо и элемент, который был последним, оказывается на первом месте. Спустя options.speed микросекунд линейке присваивается прежнее значение «transition: none».

var elm, buf, this$ = this;
elm = this.crslList.lastElementChild;
buf = elm.cloneNode(true); this.crslList.insertBefore(buf, this.crslList.firstElementChild);
this.crslList.removeChild(elm);
this.crslList.style.marginLeft = '-' + this.elemWidth + 'px';
window.getComputedStyle(this.crslList). marginLeft;
this.crslList.style.cssText = 'transition: margin '+this.options.speed+'ms ease;';
this.crslList.style.marginLeft = '0px';
setTimeout(function() {
	this$.crslList.style.cssText = 'transition: none;'
}, this.options.speed);


Если нужно прокрутить n элементов одновременно, перестановка элементов осуществляется в цикле n раз, а расстояние margin-left увеличивается в n раз.

Прокрутка влево (функция elemNext)происходит в обратном порядке. Сначала линейке this.crslList присваивается свойство «transition: margin '+ options.speed+'ms ease» и линейка плавно прокручивается влево (crslList.style.marginLeft = '-' + elemWidth + 'px'). Далее спустя options.speed микросекунд первый элемент клонируется и вставляется в конец линейки, после чего первый элемент удаляется. Линейке возвращается значение «transition: none». Если нужно прокрутить n элементов одновременно, перестановка элементов так же, как и в предыдущем случае, осуществляется в цикле n раз и расстояние margin-left увеличивается в n раз.

var elm, buf, this$ = this;
this.crslList.style.cssText = 'transition: margin '+this.options.speed+'ms ease;';
this.crslList.style.marginLeft = '-' + this.elemWidth*num + 'px';
setTimeout(function() {
	this$.crslList.style.cssText = 'transition: none;';
	elm = this$.crslList.firstElementChild;
	buf = elm.cloneNode(true); this$.crslList.appendChild(buf);
	this$.crslList.removeChild(elm)
	this$.crslList.style.marginLeft = '0px'
}, this.options.speed);


Если опция цикла выключена, то в этом случае перестановок элементов нет, а вся линейка элементов смещается как единое целое влево или вправо до своих крайних точек. Линейке элементов this.crslList свойство «transition: margin '+ options.speed+'ms ease» присваивается ещё при инициализации карусели и больше не удаляется.

Вызов карусели производится по имени класса ant-carousel или по идентификатору. Во втором случае можно разместить несколько каруселей на одной странице. Файл index.html с каруселью может выглядеть так:




	
	
	Ant-Carousel
	
	


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


Полный текст программы:

Javascript
function Ant(crslId) {

	var id = document.getElementById(crslId);
	if(id) {
		this.crslRoot = id
	}
	else {
		this.crslRoot = document.querySelector('.ant-carousel')
	};

	// Carousel objects
	this.crslList = this.crslRoot.querySelector('.ant-carousel-list');
	this.crslElements = this.crslList.querySelectorAll('.ant-carousel-element');
	this.crslElemFirst = this.crslList.querySelector('.ant-carousel-element');
	this.leftArrow = this.crslRoot.querySelector('div.ant-carousel-arrow-left');
	this.rightArrow = this.crslRoot.querySelector('div.ant-carousel-arrow-right');
	this.indicatorDots = this.crslRoot.querySelector('div.ant-carousel-dots');

	// Initialization
	this.options = Ant.defaults;
	Ant.initialize(this)
};

Ant.defaults = {

	// Default options for the carousel
	elemVisible: 3, // Кол-во отображаемых элементов в карусели
	loop: true,     // Бесконечное зацикливание карусели 
	auto: true,     // Автоматическая прокрутка
	interval: 5000, // Интервал между прокруткой элементов (мс)
	speed: 750,     // Скорость анимации (мс)
	touch: true,    // Прокрутка  прикосновением
	arrows: true,   // Прокрутка стрелками
	dots: true      // Индикаторные точки
};

Ant.prototype.elemPrev = function(num) {
	num = num || 1;

	if(this.options.dots) this.dotOn(this.currentElement);
	this.currentElement -= num; if(this.currentElement < 0) this.currentElement = this.dotsVisible-1;
	if(this.options.dots) this.dotOff(this.currentElement);

	if(!this.options.loop) {  // сдвиг вправо без цикла
		this.currentOffset += this.elemWidth*num;
		this.crslList.style.marginLeft = this.currentOffset + 'px';
		if(this.currentElement == 0) {
			this.leftArrow.style.display = 'none'; this.touchPrev = false
		}
		this.rightArrow.style.display = 'block'; this.touchNext = true
	}
	else {                    // сдвиг вправо с циклом
		var elm, buf, this$ = this;
		for(let i=0; i= this.dotsVisible) this.currentElement = 0;
	if(this.options.dots) this.dotOff(this.currentElement);

	if(!this.options.loop) {  // сдвиг влево без цикла
		this.currentOffset -= this.elemWidth*num;
		this.crslList.style.marginLeft = this.currentOffset + 'px';
		if(this.currentElement == this.dotsVisible-1) {
			this.rightArrow.style.display = 'none'; this.touchNext = false
		}
		this.leftArrow.style.display = 'block'; this.touchPrev = true
	}
	else {                    // сдвиг влево с циклом
		var elm, buf, this$ = this;
		this.crslList.style.cssText = 'transition:margin '+this.options.speed+'ms ease;';
		this.crslList.style.marginLeft = '-' + this.elemWidth*num + 'px';
		setTimeout(function() {
			this$.crslList.style.cssText = 'transition:none;';
			for(let i=0; i 20 && Math.abs(xDiff) > Math.abs(yDiff) && new Date().getTime() - dragTime < 80) {
				if(that.touchNext && xDiff > 0) {that.elemNext()}
				else if(that.touchPrev && xDiff < 0) {that.elemPrev()}
			}
		}, false)
	};

	if(that.options.arrows) {  // инициализация стрелок
		if(!that.options.loop) that.crslList.style.cssText = 'transition:margin '+that.options.speed+'ms ease;';
		that.leftArrow.addEventListener('click', function() {that.elemPrev()}, false);
		that.rightArrow.addEventListener('click', function() {that.elemNext()}, false)
	}
	else {
		that.leftArrow.style.display = 'none'; that.rightArrow.style.display = 'none'
	};

	if(that.options.dots) {  // инициализация индикаторных точек
		var sum = '', diffNum;
		for(let i=0; i that.currentElement) {
					that.elemNext(diffNum)
				}
				// Если n == that.currentElement ничего не делаем
			}, false)
		};
		that.dotOff(0);  // точка[0] выключена, остальные включены
		for(let i=1; i


Возможный внешний вид карусели для трёх элементов:

image
Спасибо за внимание!

© Habrahabr.ru