Ant-карусель на CSS и Javascript

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

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

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

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

HTML

<div class="ant-carousel">   <div class="ant-carousel-hider">     <ul class="ant-carousel-list">       <li class="ant-carousel-element"><img src="images/img1.jpg" alt="1"> <p>Описание 1</p> </li>       <li class="ant-carousel-element"><img src=" images /img2.jpg" alt="2"> <p>Описание2</p> </li> 	…       <li class="ant-carousel-element"><img src=" images /imgN.jpg" alt="N"> <p>Описание N</p> </li>     </ul>   </div>   <div class="ant-carousel-arrow-left"></div><div class="ant-carousel-arrow-right"></div>   <div class="ant-carousel-dots"></div> </div> 

Здесь использованы элементы <ul><li>, но вместо них можно использовать <div > <div >, если вам это удобнее. Стрелки и индикаторные точки располагаются абсолютным позиционированием в соответствующих контейнерах. Для стрелок используются рисунки в виде треугольных скобок, которые, при желании, вы можете заменить своими рисунками или генерацией изображения псевдо-элементами :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 с каруселью может выглядеть так:

<!DOCTYPE html> <html lang="ru"> <head> 	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 	<meta name="viewport" content="width=device-width; initial-scale=1.0"> 	<title>Ant-Carousel</title> 	<!-- подключение стилей --> 	<link rel="stylesheet" type="text/css" href="ant-files/ant-carousel-styles.css"> </head> <body> 	… 	<div class="ant-carousel"> 		<!-- здесь ваша карусель --> 		… 	</div> 	… 	<footer> 	… 	</footer> <!-- подключение карусели --> <script src="ant-files/ant-carousel. js"></script> <!-- вызов карусели --> <script>new Ant()</script> </body> </html> 

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

<div class="ant-carousel" id=”first”> <div class="ant-carousel" id=”first”> 	<!-- первая карусель --> 	… <div class="ant-carousel" id=”second”> 	<!-- вторая карусель --> 	… <script>new Ant(“first”); new Ant(“second”);</script> 

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

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<num; i++) { 			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*num + 'px'; 		var compStyle = window.getComputedStyle(this.crslList).marginLeft || this.crslList.currentStyle.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) 	} };  Ant.prototype.elemNext = function(num) { 	num = num || 1;  	if(this.options.dots) this.dotOn(this.currentElement); 	this.currentElement += num; if(this.currentElement >= 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<num; i++) { 				elm = this$.crslList.firstElementChild; 				buf = elm.cloneNode(true); this$.crslList.appendChild(buf); 				this$.crslList.removeChild(elm) 			}; 			this$.crslList.style.marginLeft = '0px' 		}, this.options.speed) 	} };  Ant.prototype.dotOn = function(num) { 	this.indicatorDotsAll[num].style.cssText = 'background-color:#BBB; cursor:pointer;' };  Ant.prototype.dotOff = function(num) { 	this.indicatorDotsAll[num].style.cssText = 'background-color:#556; cursor:default;' };  Ant.initialize = function(that) {  	// Constants 	that.elemCount = that.crslElements.length; // Количество элементов 	that.dotsVisible = that.elemCount;         // Число видимых точек 	var elemStyle = window.getComputedStyle(that.crslElemFirst) || that.crslElemFirst.currentStyle; 	that.elemWidth = that.crslElemFirst.offsetWidth +  // Ширина элемента (без margin) 	  parseInt(elemStyle.marginLeft) + parseInt(elemStyle.marginRight);  	// Variables 	that.currentElement = 0; that.currentOffset = 0; 	that.touchPrev = true; that.touchNext = true; 	var xTouch, yTouch, xDiff, yDiff, dragTime;  	// Functions 	function setAutoScroll() { 		that.autoScroll = setInterval(that.elemNext.bind(that), that.options.interval) 	};  	// Start initialization 	if(that.elemCount <= that.options.elemVisible) {   // Отключить навигацию 		that.options.auto = false; that.options.touch = false; that.options.arrows = false; that.options.dots = false; 		that.leftArrow.style.display = 'none'; that.rightArrow.style.display = 'none' 	};  	if(!that.options.loop) {       // если нет цикла - уточнить количество точек 		that.dotsVisible = that.elemCount - that.options.elemVisible + 1; 		that.leftArrow.style.display = 'none';  // отключить левую стрелку 		that.touchPrev = false;    // отключить прокрутку прикосновением вправо 		that.options.auto = false; // отключить автопркрутку 	} 	else if(that.options.auto) {   // инициализация автопрокруки 		setAutoScroll(); 		// Остановка прокрутки при наведении мыши на элемент 		that.crslList.addEventListener('mouseenter', function() {clearInterval(that.autoScroll)}, false); 		that.crslList.addEventListener('mouseleave', setAutoScroll, false) 	};  	if(that.options.touch) {   // инициализация прокрутки прикосновением 		that.crslList.addEventListener('touchstart', function(e) { 			xTouch = parseInt(e.touches[0].clientX); yTouch = parseInt(e.touches[0].clientY); 			dragTime = new Date().getTime() 		}, false); 		that.crslList.addEventListener('touchmove', function(e) { 			if(!xTouch || !yTouch) return; 			xDiff = xTouch - parseInt(e.touches[0].clientX); 			yDiff = yTouch - parseInt(e.touches[0].clientY); 			if(Math.abs(xDiff) > 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.dotsVisible; i++) { 			sum += '<span class="ant-dot"></span>' 		}; 		that.indicatorDots.innerHTML = sum; 		that.indicatorDotsAll = that.crslRoot.querySelectorAll('span.ant-dot'); 		// Назначаем точкам обработчик события 'click' 		for(let n=0; n<that.dotsVisible; n++) { 			that.indicatorDotsAll[n].addEventListener('click', function() { 				diffNum = Math.abs(n - that.currentElement); 				if(n < that.currentElement) { 					that.elemPrev(diffNum) 				} 				else if(n > that.currentElement) { 					that.elemNext(diffNum) 				} 				// Если n == that.currentElement ничего не делаем 			}, false) 		}; 		that.dotOff(0);  // точка[0] выключена, остальные включены 		for(let i=1; i<that.dotsVisible; i++) { 			that.dotOn(i) 		} 	} }; 

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

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

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

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

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