Slider
W poniższym tekście postaramy się zrobić dynamiczny slider.
HTML i CSS
Zacznijmy od struktury HTML. Nasz slider będzie zwykłą listą elementów. Mogą nimi być poszczególne elementy listy, kolejne artykuły czy podobne rzeczy - po prostu będzie to zbiór jakiś elementów. W poniższym kodzie mamy zwykłą listę artykułów:
<div class="elements-list" id="slider1">
<article class="element">
<h2 class="element-title">
Jestem tytułem slajdu 1
</h2>
<div class="element-text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit...
</div>
</article>
<article class="element">
<h2 class="element-title">
Jestem tytułem slajdu 2
</h2>
<div class="element-text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit...
</div>
</article>
</div>
Powyższą strukturę za pomocą Javascript musimy zamienić na nasz slider. Będzie on miał strukturę taką jak na poniższej grafice:

Czemu od razu nie stworzymy pełnej struktury slidera z przyciskami next/prev i paginacją? Oczywiście możemy. W ramach treningu jednak spróbujmy pójść o krok dalej. Nasz plugin będzie bardziej uniwersalny.
Masz więc listę artykułów na stronie. Chcesz z nich zrobić slider? Proszę bardzo - użyj naszego kodu. Chcesz ją zamienić na accordion? Użyj innego pluginu. A może ktoś na tą stronę wejdzie bez Javascriptu? Zobaczy początkową listę artykułów. Dodatkowo dzięki temu, że część elementów będzie generowana dynamicznie, będziemy mieli większe możliwości - bo np. nie każdy slider musi mieć paginację, albo przyciski poprzedni/następny.
Całość ostylujemy za pomocą grida oraz pozycjonowania absolutnego dla dolnego stronicowania.
Pokaż HTMLPokaż CSS
klasa Slider
Zakładamy, że sliderów na stronie możemy mieć kilka, więc powinniśmy je zrobić pod wspólny wzór. Skorzystamy tutaj z klasy.
class Slider {
constructor(elemSelector) {
this.currentSlide = 0; //aktualny slide
this.sliderSelector = elemSelector; //selektor elementu który zamienimy na slider
this.elem = null; //tutaj pobierzemy element który zamienimy na slider
this.slider = null; //tutaj wygenerujemy slider
this.slides = null; //tutaj pobierzemy slajdy
this.prev = null; //przycisk prev
this.next = null; //przycisk next
this.dots = []; //przyciski kropek
}
}
Metoda generateSlider()
Pierwszą metodą jaką napiszemy to metoda zamieniająca powyższą strukturę html na odpowiednią.
class Slider {
...
generateSlider() {
//pobieramy element który zamienimy na slider
this.slider = document.querySelector(this.sliderSelector);
this.slider.classList.add("slider");
//tworzymy kontener dla slajdów
const slidesCnt = document.createElement("div");
slidesCnt.classList.add("slider-slides-cnt");
//pobieramy element slajdów
this.slides = this.slider.children;
//to jest żywa kolekcja, więc przy przeniesieniu każdego slajdu
//jej długość maleje
while (this.slides.length) {
this.slides[0].classList.add("slider-slide");
//jeżeli element dodajemy do innego elementu
//to tak jakbyśmy go usuwali z tej kolekcji
//bo jeden element nie może być równocześnie w dwóch miejscach naraz
slidesCnt.append(this.slides[0]);
}
//musimy na nowo pobrać slajdy, bo powyższa kolekcja jest już pusta
this.slides = slidesCnt.querySelectorAll(".slider-slide");
//wygenerowaliśmy kontener ze slajdami, wstawiamy go więc do slidera
this.slider.append(slidesCnt);
}
}
Po pierwsze pobieramy nasz przyszły slider.
Wszystkie slajdy muszą znaleźć się w jednym wspólnym elemencie. W naszym przypadku będzie to stworzony dynamicznie .slider-slides-cnt.
Aby przerzucić do niego elementy slidera, pobieramy je za pomocą właściwości children
, a następnie robiąc po nich pętlę wrzucamy za pomocą append
do powyższego elementu.
Powyższą metodę generateSlider() od razu odpalamy w konstruktorze:
class Slider {
constructor(elemSelector) {
this.currentSlide = 0; //aktualny slide
this.sliderSelector = elemSelector; //selektor elementu który zamienimy na slider
this.elem = null; //tutaj pobierzemy element który zamienimy na slider
this.slider = null; //tutaj wygenerujemy slider
this.slides = null; //tutaj pobierzemy slajdy
this.prev = null; //przycisk prev
this.next = null; //przycisk next
this.dots = []; //przyciski kropek
this.generateSlider();
}
...
}
Metoda createPrevNext()
Kontynuujemy generowanie struktury html. Kolejną metodą będzie createPrevNext(), która stworzy dla naszego slidera przyciski "Poprzedni i następny slide":
class Slider {
...
createPrevNext() {
//generujemy przycisk "Poprzedni slajd"
this.prev = document.createElement("button");
this.prev.type = "button";
this.prev.innerText = "Poprzedni slide";
this.prev.classList.add("slider-button");
this.prev.classList.add("slider-button-prev");
this.prev.addEventListener("click", this.slidePrev.bind(this));
//generujemy przycisk "Następny slajd"
this.next = document.createElement("button");
this.next.type = "button";
this.next.innerText = "Następny slide";
this.next.classList.add("slider-button");
this.next.classList.add("slider-button-next");
this.next.addEventListener("click", this.slideNext.bind(this));
//wrzucamy je do wspólnego diva
//który dam nam ciut większe możliwości stylowania
const nav = document.createElement("div");
nav.classList.add("slider-nav");
nav.appendChild(this.prev);
nav.appendChild(this.next);
//diva z przyciskami wrzucamy do slajdu
this.slider.appendChild(nav);
}
}
Żeby mieć w klikniętym przycisku dostęp do właściwości metod naszego slidera, musimy je przypiąć za pomocą metod bind() lub korzystając z funkcji strzałkowej:
this.prev.addEventListener("click", this.slidePrev.bind(this));
//lub
this.prev.addEventListener("click", () => this.slidePrev());
Cała reszta kodu to stworzenie 2 przycisków i ustawienie im podstawowych właściwości.
Po kliknięciu na te przyciski odpalimy właściwości slidePrev() i slideNext(). Napiszemy je za chwilę.
Powyższą metodę odpalmy na końcu metody generateSlider:
class Slider {
...
generateSlider() {
...
this.createPrevNext();
}
}
Metoda createPagination()
Kolejną czynnością jest stworzenie metody generującej kropki zmieniające slajdy:
class Slider {
...
createPagination() {
const ulDots = document.createElement("ul");
ulDots.classList.add("slider-pagination");
//tworzymy pętlę w ilości liczby slajdów
for (let i=0; i<this.slides.length; i++) {
//każdorazowo tworzymy LI wraz z buttonem
//każdy button po kliknięciu zmieni slajd
//za pomocą metody changeSlide()
const li = document.createElement("li");
li.classList.add("slider-pagination-element");
const btn = document.createElement("button");
btn.classList.add("slider-pagination-button");
btn.type = "button";
btn.innerText = i+1;
btn.setAttribute("aria-label", `Ustaw slajd ${i+1}`);
btn.addEventListener("click", () => this.changeSlide(i));
li.appendChild(btn);
ulDots.appendChild(li);
//wygenerowany przycisk wrzucamy do wspólnej tablicy
//dzięki temu potem łatwiej będzie nam się do tych kropek odwoływać
this.dots.push(li);
}
this.slider.appendChild(ulDots);
}
}
Tak samo jak przy metodzie createPrevNext() powyższą metodę też odpalamy w metodzie generateSlider:
class Slider {
generateSlider() {
...
this.createPrevNext();
this.createPagination();
}
}
Odpalmy nasz slider i zobaczmy co w tej chwili mamy:
const slide = new Slider("#slider1");
Demo 1 - wygenerowana struktura html
Metoda changeSlide()
Po kliknięciu na kropkę odpalamy metodę changeSlide(), która ma przełączyć slajd. Metoda usuwa więc wszystkim slajdom klasę .slider-slide-active i dodaje ją tylko aktualnemu. Żeby zrobić pętlę forEach po kolekcji divów, skorzystamy ze sztuczki, którą poznaliśmy tutaj:
class Slider {
...
changeSlide(index) {
//robimy pętlę po slajdach usuwając klasę active
this.slides.forEach(slide => {
slide.classList.remove("slider-slide-active");
slide.setAttribute("aria-hidden", true);
});
//dodajemy ją tylko wybranemu
this.slides[index].classList.add("slider-slide-active");
this.slides[index].setAttribute("aria-hidden", false);
//podobny manewr robimy dla kropek
this.dots.forEach(dot => {
dot.classList.remove("slider-pagination-element-active");
});
this.dots[index].classList.add("slider-pagination-element-active");
//aktualny slide przestawiamy na wybrany
this.currentSlide = index;
}
}
Dodatkowo wykorzystajmy tą metodę do ustawienia początkowego slajdu odpalając ją na końcu konstruktora:
class Slider {
constructor(elemSelector) {
this.sliderSelector = elemSelector;
this.currentSlide = 0;
this.slider = null;
this.elem = null;
this.slides = null;
this.prev = null;
this.next = null;
this.dots = [];
this.generateSlider();
this.changeSlide(this.currentSlide);
}
...
}
Metody slidePrev() i slideNext()
Powyższa metoda zmienia slajd. Klikając na przyciski "poprzedni-następny" też będziemy zmieniać slajd, tylko nie na konkretny numer, a o jeden więcej lub mniej.
Jak spojrzysz na początkowe zmienne, mamy tam zmienną currentSlide. Określa ona aktualny slajd.
Chcemy by przyciski "poprzedni-następny" działały w pętli. Jeżeli dojdziemy do ostatniego slajdu, to kolejne przełączenie powinno przestawić slider na pierwszy slajd. I w drugą stronę - jeżeli klikniemy poprzedni będąc na pierwszym slajdzie, powinien się wyświetlić ostatni. Wykorzystajmy do tego wspomnianą zmienną:
class Slider {
...
slidePrev() {
this.currentSlide--;
if (this.currentSlide < 0) {
this.currentSlide = this.slides.length - 1;
}
this.changeSlide(this.currentSlide);
}
slideNext() {
this.currentSlide++;
if (this.currentSlide > this.slides.length - 1) {
this.currentSlide = 0;
}
this.changeSlide(this.currentSlide);
}
}
W poniższym przykładzie lekko zmieniłem za pomocą styli czcionkę i tło:
Demo 2 - działające przyciskiAutomatyzacja
Do automatycznego przełączania slajdów możemy wykorzystać setTimeout(). Wystarczy za pomocą tej funkcji wywołać powyższą metodę slideNext()
.
W metodzie init odpalamy metodę changeSlide()
- tak by po wejściu na stronę był widoczny pierwszy slajd. Po kliknięciu na przyciski poprzedni-następny też wywołujemy metodę changeSlide()
. Jeżeli chcemy by po zmianie slajdu kolejna zmiana nastąpiła automatycznie, wystarczy na końcu tej metody dodać nasz setTimeout, który wywoła nam jeszcze raz metodę changeSlide()
:
class Slider {
constructor() {
...
this.time = null;
...
}
...
changeSlide(index) {
...
clearInterval(this.time);
this.time = setTimeout(() => this.slideNext(), 6000); //co 6 sekund automatycznie się przełączy
}
}
Jeżeli ktoś będzie szybko zmieniał slajdy (klikał po przyciskach lub kropkach), każdorazowo zostanie odpalone czyszczenie aktualnie odpalonego timeOuta i każdorazowo będzie on ustawiany na nowo. Dlatego nasze automatyczne przełączenie slajdu zadziała dopiero gdy użytkownik się uspokoi.
Opcje, więcej opcji
W powyższej metodzie ustawiliśmy przełączanie slajdów co 6 sekund. Aż prosi się, by przy wywołaniu naszego slidera programista mógł ustawić takie wartości.
Zastosujemy tutaj podejście, które dokładniej opisałem tutaj. Do naszego konstruktora trafi obiekt z opcjami, które ustawi programista. Opcje te nadpiszą domyślne ustawienia, w wyniku czego dostaniemy nowy scalony obiekt z ustawieniami slidera.
const opts = {
width : 100
}
const defaultOpts = {
width: 10,
height: 200
}
const options = { ...defaultOptions, ...opts }
console.log(options); //{width: 100, height:200}
lub
const opts = {
width : 100
}
const defaultOptions = {
width: 10,
height: 200
}
const options = Object.assign({}, defaultOptions, opts);
console.log(options); //{width: 100, height:200}
Dodajmy domyślne ustawienia w konstruktorze:
class Slider {
constructor(elemSelector, opts) {
this.slider = document.querySelector(elemSelector);
this.slides = this.slider.querySelectorAll(".slider-slide");
this.currentSlide = 0;
this.prev = null;
this.next = null;
this.dots = [];
this.time = null;
const defaultOpts = {
pauseTime : 0
};
this.options = Object.assign({}, defaultOpts, opts);
this.generateSlider();
this.changeSlide(this.currentSlide);
}
...
}
Domyślnie czas przerwy między kolejnym przełączaniem będzie wynosił 0. Jeżeli programista tego nie zmieni, nie będziemy automatycznie przełączać slajdów:
class Slider {
...
changeSlide(index) {
this.slides.forEach(slide => {
slide.classList.remove("slider-slide-active");
slide.setAttribute("aria-hidden", true);
});
this.slides[index].classList.add("slider-slide-active");
this.slides[index].setAttribute("aria-hidden", false);
if (this.options.generateDots) {
this.dots.forEach(dot => {
dot.classList.remove("slider-pagination-element-active");
});
this.dots[index].classList.add("slider-pagination-element-active");
}
this.currentSlide = index;
if (typeof this.options.pauseTime === "number" && this.options.pauseTime !== 0) {
clearInterval(this.time);
this.time = setTimeout(() => this.slideNext(), this.options.pauseTime);
}
}
}
Podobne opcje możemy ustawić dla innych elementów naszego slidera. Dla przykładu możemy dodać opcję do zmiany tekstów na przyciskach:
class Slider {
constructor(elemSelector, opts) {
this.currentSlide = 0;
this.slider = document.querySelector(elemSelector);
this.slides = this.slider.querySelectorAll(".slider-slide");
this.prev = null; //przycisk prev
this.next = null; //przycisk next
this.dots = [];
this.time = null;
const defaultOpts = {
pauseTime : 0,
prevText : "Poprzedni slide",
nextText : "Następny slide"
};
this.options = Object.assign({}, defaultOpts, opts);
this.generateSlider();
this.changeSlide(this.currentSlide);
}
...
}
class Slider {
...
createPrevNext() {
this.prev = document.createElement("button");
this.prev.type = "button";
this.prev.innerText = this.options.prevText;
this.prev.classList.add("slider-button");
this.prev.classList.add("slider-button-prev");
this.prev.addEventListener("click", this.slidePrev.bind(this));
this.next = document.createElement("button");
this.next.type = "button";
this.next.innerText = this.options.nextText;
this.next.classList.add("slider-button");
this.next.classList.add("slider-button-next");
this.next.addEventListener("click", this.slideNext.bind(this));
const nav = document.createElement("div");
nav.classList.add("slider-nav");
nav.appendChild(this.prev);
nav.appendChild(this.next);
this.slider.appendChild(nav);
}
}
Dodatkowe dwie opcje, które dodamy to możliwość wyłączenia generowania przycisków "poprzedni-następny" jak i generowania kropek:
class Slider {
...
generateSlider() {
//pobieramy element który zamienimy na slider
this.slider = document.querySelector(this.sliderSelector);
this.slider.classList.add("slider");
//tworzymy kontener dla slajdów
const slidesCnt = document.createElement("div");
slidesCnt.classList.add("slider-slides-cnt");
//pobieramy element slajdów
this.slides = this.slider.children;
//to jest żywa kolekcja, więc przy przeniesieniu każdego slajdu
//jej długość maleje
while (this.slides.length) {
this.slides[0].classList.add("slider-slide");
slidesCnt.appendChild(this.slides[0]);
}
//musimy na nowo pobrać slajdy, bo powyższa kolekcja jest już pusta
this.slides = slidesCnt.querySelectorAll(".slider-slide");
//wygenerowaliśmy kontener ze slajdami, wstawiamy go więc do slidera
this.slider.appendChild(slidesCnt);
if (this.options.generatePrevNext) this.createPrevNext();
if (this.options.generateDots) this.createPagination();
}
}
Oraz w metodzie changeSlide()
, która działa na kropkach.
class Slider {
changeSlide(index) {
this.slides.forEach(slide => {
slide.classList.remove("slider-slide-active");
slide.setAttribute("aria-hidden", true);
});
this.slides[index].classList.add("slider-slide-active");
this.slides[index].setAttribute("aria-hidden", false);
if (this.options.generateDots) {
this.dots.forEach(dot => {
dot.classList.remove("slider-pagination-element-active");
});
this.dots[index].classList.add("slider-pagination-element-active");
}
this.currentSlide = index;
if (typeof this.options.pauseTime === "number" && this.options.pauseTime !== 0) {
clearInterval(this.time);
this.time = setTimeout(() => this.slideNext(), this.options.pauseTime)
}
}
...
}
Od tej chwili programista używając naszego slidera będzie mógł podawać także jego opcje:
//slider1
const opts1 = {
pauseTime : 0,
prevText : "Poprzedni",
nextText : "Następny"
}
const slide1 = new Slider("#slider1", opts1);
//slider2
const opts2 = {
pauseTime : 5000,
generatePrevNext : false
}
const slide2 = new Slider("#slider2", opts2);
Demo
Poniższy przykład - już końcowy - zawiera różne slidery wraz z dodatkowym stylowaniem, którym zmieniłem ich wygląd. Dla strzałek zastosowałem https://www.bootstrapcdn.com/fontawesome/ wraz ikonami (lewo, prawo) dla strzałek
, oraz naprawdę lekkim efektem przy najechaniu na nie kursorem. Demo