https://habr.com/ru/post/467079/- Разработка веб-сайтов
- 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>
Полный текст программы:
Javascriptfunction 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)
}
}
};
Возможный внешний вид карусели для трёх элементов:

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