JavaScript. Простой слайдер
20.05.2022
Теги: CSS • Frontend • HTML • JavaScript • Web-разработка • Изображение • Практика
Простой слайдер на чистом css и javascript, не имеет практической ценности, исключительно для практики. Несколько картинок, возможность автопрокрутки, кнопки вперед и назад, индикатор текущего слайда. Для начала подготовим простую страницу, создадим html-разметку и файл стилей — а потом напишем js-класс Slider
.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Slider</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .container { max-width: 900px; margin: 0px auto; } </style> <link rel="stylesheet" href="slider.css"> <script src="slider.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { // здесь будем создавать экземпляр js-класса слайдера }); </script> </head> <body> <div class="container"> <!-- здесь будет html-разметка слайдера --> </div> </body> </html>
Создаем html-разметку слайдера
<body> <div class="container"> <div class="carousel"> <div class="carousel-window"> <div class="carousel-slides"> <div class="carousel-item"> <img src="img/slide-1.jpg" alt=""> </div> <div class="carousel-item"> <img src="img/slide-2.jpg" alt=""> </div> <div class="carousel-item"> <img src="img/slide-3.jpg" alt=""> </div> <div class="carousel-item"> <img src="img/slide-4.jpg" alt=""> </div> <div class="carousel-item"> <img src="img/slide-5.jpg" alt=""> </div> </div> </div> <a href="#" class="carousel-prev"> <span class="carousel-prev-icon"><</span> </a> <a href="#" class="carousel-next"> <span class="carousel-next-icon">></span> </a> </div> </div> </body>
Создаем файл стилей слайдера
.carousel {
margin: 0 auto;
position: relative;
}
.carousel .carousel-indicators {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: 15;
display: flex;
justify-content: center;
padding: 10px;
list-style: none;
}
.carousel .carousel-indicators li {
flex: 0 1 auto;
width: 20px;
height: 20px;
margin-right: 5px;
margin-left: 5px;
cursor: pointer;
background-color: #343a40;
border: 2px solid #fff;
border-radius: 50%;
}
.carousel .carousel-indicators .active {
background-color: #025fff;
}
.carousel .carousel-window {
width: 100%;
height: 500px;
position: relative;
overflow: hidden;
}
.carousel .carousel-slides {
height: 100%;
display: flex;
transition: transform 0.5s;
}
.carousel .carousel-item {
height: 100%;
}
.carousel .carousel-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.carousel .carousel-prev, .carousel .carousel-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
width: 15%;
color: #fff;
opacity: 0.7;
text-align: center;
font-size: 40px;
text-decoration: none;
}
.carousel .carousel-prev:hover, .carousel .carousel-next:hover {
opacity: 1;
background-color: rgba(0, 0, 0, 0.2);
}
.carousel .carousel-prev {
left: 0;
}
.carousel .carousel-next {
right: 0;
}
Создаем js-класс слайдера
/* * <div class="carousel"> width: 100% (от контейнера .container 900px) * <div class="carousel-window"> width: 100% (от родителя 900px); height: 500px * <div class="carousel-slides"> display: flex, style.width = 300% (2700px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * </div> * </div> * </div> * Можно сказать, что carousel-window представляет собой окно просмотра 900x500px, * в этом окне просмотра виден одновременно только один кадр (слайд). Элемент * carousel-slides представляет из себя цепочку из трех кадров (как в кино). Эти * кадры выстроены по горизонтали благодаря display:flex. При клике на кнопки * next и prev — цепочка смещается влево, и в окне просмотра появляется очередной * кадр (слайд). */ class Slider { constructor(slider, autoplay = true) { // элемент div.carousel this.slider = slider; // все кадры (слайды) this.allFrames = slider.querySelectorAll('.carousel-item'); // цепочка кадров this.frameChain = slider.querySelector('.carousel-slides'); // кнопка «вперед» this.nextButton = slider.querySelector('.carousel-next'); // кнопка «назад» this.prevButton = slider.querySelector('.carousel-prev'); this.index = 0; // индекс кадра, который сейчас в окне просмотра this.length = this.allFrames.length; // сколько всего есть кадров this.autoplay = autoplay; // включить автоматическую прокрутку? this.paused = null; // чтобы можно было выключать автопрокрутку this.init(); // инициализация слайдера } init() { this.dotButtons = this.dots(); // создать индикатор текущего кадра // все кадры должны быть одной ширины, равной ширине окна просмотра; // если кадров три, то ширина каждого кадра будет 100/3 = 33.33333% // от ширины контейнера .carousel-slides, то есть 900 пикселей this.allFrames.forEach(frame => frame.style.width = 100/this.length + '%'); // ширина цепочки кадров должна равна ширине всех кадров, то есть // 900*3 = 2700 пикселей; но удобнее задать в процентах от родителя, // если кадров три, то ширина контейнера кадров будет 100*3 = 300% this.frameChain.style.width = 100 * this.length + '%'; this.nextButton.addEventListener('click', event => { // клик по кнопке «вперед» event.preventDefault(); this.next(); }); this.prevButton.addEventListener('click', event => { // клик по кнопке «назад» event.preventDefault(); this.prev(); }); // клики по кнопкам индикатора текущего кадра this.dotButtons.forEach(dot => { dot.addEventListener('click', event => { event.preventDefault(); const index = this.dotButtons.indexOf(event.target); if (index === this.index) return; this.goto(index); }); }); if (this.autoplay) { // включить автоматическую прокрутку? this.play(); // когда мышь над слайдером — останавливаем автоматическую прокрутку this.slider.addEventListener('mouseenter', () => clearInterval(this.paused)); // когда мышь покидает пределы слайдера — опять запускаем прокрутку this.slider.addEventListener('mouseleave', () => this.play()); } } // перейти к кадру с индексом index goto(index) { // изменить текущий индекс... if (index > this.length - 1) { this.index = 0; } else if (index < 0) { this.index = this.length - 1; } else { this.index = index; } // ...и выполнить смещение this.move(); } // перейти к следующему кадру next() { this.goto(this.index + 1); } // перейти к предыдущему кадру prev() { this.goto(this.index - 1); } // рассчитать и выполнить смещение move() { // на сколько нужно сместить, чтобы нужный кадр попал в окно const offset = 100/this.length * this.index; this.frameChain.style.transform = `translateX(-${offset}%)`; this.dotButtons.forEach(dot => dot.classList.remove('active')); this.dotButtons[this.index].classList.add('active'); } // запустить автоматическую прокрутку play() { this.paused = setInterval(() => this.next(), 3000); } // создать индикатор текущего слайда dots() { const ol = document.createElement('ol'); ol.classList.add('carousel-indicators'); const children = []; for (let i = 0; i < this.length; i++) { let li = document.createElement('li'); if (i === 0) li.classList.add('active'); ol.append(li); children.push(li); } this.slider.prepend(ol); return children; } }
Создаем экземпляр js-класса
<link rel="stylesheet" href="slider.css"> <script src="slider.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { new Slider(document.querySelector('.carousel')); }); </script>
Еще одни вариант
Еще один вариант слайдера, который допускает показ в каждом кадре нескольких элементов. Кроме того, можно указать, на сколько элементов сдвигать, чтобы показать следующий кадр.
/* * <div class="carousel"> width: 100% (от контейнера .container 900px) * <div class="carousel-window"> width: 100% (от родителя 900px); height: 500px * <div class="carousel-slides"> display: flex, style.width = 300% (2700px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * <div class="carousel-item">...</div> style.width = 33.33333% (900px) * </div> * </div> * </div> */ class Slider { constructor(slider, {autoplay = true, inFrame = 1, offset = 1} = {}) { // элемент div.carousel this.slider = slider; // кол-во элементов в одном кадре this.inFrame = inFrame; // на сколько элементов смещать this.offset = offset; // все элементы слайдера this.allItems = slider.querySelectorAll('.carousel-item'); // сколько всего элементов this.itemCount = this.allItems.length; // все кадры слайдера this.allFrames = this.frames(); // сколько всего кадров this.frameCount = this.allFrames.length; // индекс кадра в окне просмотра this.frameIndex = 0; // контейнер для элементов this.wrapper = slider.querySelector('.carousel-slides'); // кнопка «вперед» this.nextButton = slider.querySelector('.carousel-next'); // кнопка «назад» this.prevButton = slider.querySelector('.carousel-prev'); this.autoplay = autoplay; // включить автоматическую прокрутку? this.paused = null; // чтобы можно было выключать автопрокрутку this.init(); // инициализация слайдера } init() { this.dotButtons = this.dots(); // создать индикатор текущего кадра // если всего 10 элементов, то ширина одного элемента составляет 1/10 // ширины контейнера .carousel-slides, то есть 100/10 = 10% this.allItems.forEach(item => item.style.width = 100 / this.itemCount + '%'); // ширина контейнера должна вмещать все элементы: если элементов 10, // в окне просмотра 3 элемента, тогда ширина контейнера равна ширине // трех окон просмотра (300%) плюс ширина одного элемента 33.33333%, let wrapperWidth = this.itemCount / this.inFrame * 100; this.wrapper.style.width = wrapperWidth + '%'; this.nextButton.addEventListener('click', event => { // клик по кнопке «вперед» event.preventDefault(); this.next(); }); this.prevButton.addEventListener('click', event => { // клик по кнопке «назад» event.preventDefault(); this.prev(); }); // клики по кнопкам индикатора текущего кадра this.dotButtons.forEach(dot => { dot.addEventListener('click', event => { event.preventDefault(); const frameIndex = this.dotButtons.indexOf(event.target); if (frameIndex === this.frameIndex) return; this.goto(frameIndex); }); }); if (this.autoplay) { // включить автоматическую прокрутку? this.play(); // когда мышь над слайдером — останавливаем автоматическую прокрутку this.slider.addEventListener('mouseenter', () => clearInterval(this.paused)); // когда мышь покидает пределы слайдера — опять запускаем прокрутку this.slider.addEventListener('mouseleave', () => this.play()); } } // перейти к кадру с индексом index goto(index) { if (index > this.frameCount - 1) { this.frameIndex = 0; } else if (index < 0) { this.frameIndex = this.frameCount - 1; } else { this.frameIndex = index; } // ...и выполнить смещение this.move(); } // перейти к следующему кадру next() { this.goto(this.frameIndex + 1); } // перейти к предыдущему кадру prev() { this.goto(this.frameIndex - 1); } // рассчитать и выполнить смещение move() { // на сколько нужно сместить, чтобы нужный кадр попал в окно const offset = 100 / this.itemCount * this.allFrames[this.frameIndex]; this.wrapper.style.transform = `translateX(-${offset}%)`; this.dotButtons.forEach(dot => dot.classList.remove('active')); this.dotButtons[this.frameIndex].classList.add('active'); } // запустить автоматическую прокрутку play() { this.paused = setInterval(() => this.next(), 3000); } // создать индикатор текущего кадра dots() { const ol = document.createElement('ol'); ol.classList.add('carousel-indicators'); const children = []; for (let i = 0; i < this.frameCount; i++) { let li = document.createElement('li'); if (i === 0) li.classList.add('active'); ol.append(li); children.push(li); } this.slider.prepend(ol); return children; } // индекс первого элемента каждого кадра frames() { // все наборы элементов, которые потенциально могут быть кадрами let temp = []; for (let i = 0; i < this.itemCount; i++) { // этот набор из this.inFrame элементов без пустого места if (this.allItems[i + this.inFrame - 1] !== undefined) { temp.push(i); } } // с учетом того, что смещение this.offset может быть больше 1, // реальных кадров будет меньше или столько же let allFrames = []; for (let i = 0; i < temp.length; i = i + this.offset) { allFrames.push(temp[i]); } // в конце могут быть элементы, которые не могут образовать целый кадр (без пустоты), // такой кадр вообще не попадает в окно просмотра; вместо него показываем последний // целый кадр из числа потенциальных; при этом смещение будет меньше this.offset if (allFrames[allFrames.length - 1] !== temp[temp.length - 1]) { allFrames.push(temp[temp.length - 1]); } return allFrames; } }
<link rel="stylesheet" href="slider.css"> <script src="slider.js"></script> <script> document.addEventListener('DOMContentLoaded', function() { new Slider(document.querySelector('.carousel'), { inFrame: 2, // два элемента в кадре offset: 1, // смещать на один элемент }); }); </script>
- JavaScript. Создание модального окна
- Плавная прокрутка, библиотека hsnaydd/moveTo
- DOM, часть 4 из 4. Атрибуты и свойства
- DOM, часть 3 из 3. Поиск элементов
- DOM, часть 2 из 3. Изменение документа
- Браузерные события, часть 5 из 5. Генерация пользовательских событий
- Браузерные события, часть 4 из 5. Действие браузера по умолчанию
Поиск: CSS • Frontend • HTML • JavaScript • Web-разработка • Изображение • Практика