Slider
W poniższym tekście postaramy się zrobić dynamiczny slider. Nie byle jaki. Całkowicie profesjonalny slider.
HTML i CSS
Zacznijmy od przygotowania odpowiedniego stylowania, oraz struktury HTML dla naszego slidera:
Pokaż HTMLPokaż CSS
klasa Slider
Zakładamy, że sliderów na stronie możemy mieć kilka, więc przy jego tworzeniu dobrze jest skorzystać z klasy.
class Slider {
constructor(query) {
this.slider = document.querySelector(query); //element slider
this.slides = [...this.slider.querySelectorAll(".slider-slide")];
this.currentSlide = Math.max(0, this.slides.findIndex(el => el.classList.contains("is-active"))); //aktualny slide
}
}
const slider = new Slider("#mySlider");
Na początku wyłapujemy ze strony slider oraz jego slajdy. Do ustawienia indexu aktualnie aktywnego slajdu wykorzystaliśmy Math.max()
. Za pomocą metody findIndex()
zwracam slajd, który posiada klasę is-active
. Może się zdarzyć, że początkowo żaden ze slajdów nie będzie miał takiej klasy. Metoda findIndex
zwróci nam wtedy -1. Korzystając z Math.max(0, -1)
zwrócimy wtedy pierwszy slajd i to właśnie jego ustawimy na aktywny. Metodę findIndex
możemy użyć dla this.slides
tylko dlatego, że wcześniej zamieniliśmy tą kolekcję na tablicę za pomocą spread operator.
Metoda createPrevNext()
Pierwszą z metod będzie createPrevNext(), która stworzy przyciski do przełączania slajdów.
class Slider {
constructor() {
...
this.createPrevNext();
}
createPrevNext() {
this.btnPrev = document.createElement("button");
this.btnPrev.type = "button";
this.btnPrev.innerText = "Poprzedni slajd";
this.btnPrev.classList.add("slider-button", "slider-button-prev");
this.btnPrev.addEventListener("click", this.slidePrev.bind(this));
this.btnNext = document.createElement("button");
this.btnNext.type = "button";
this.btnNext.innerText = "Następny slajd"
this.btnNext.classList.add("slider-button", "slider-button-next");
this.btnNext.addEventListener("click", this.slideNext.bind(this));
const nav = document.createElement("div");
nav.classList.add("slider-nav");
this.slider.append(this.btnPrev);
this.slider.append(this.btnNext);
this.slider.append(nav);
}
}
Przypisując metody do przycisków musieliśmy użyć metody bind(). Wynika to z tego, że gdy używasz klasycznych funkcji (ale i gdy podpinasz do zdarzeń funkcje przez ich nazwę) w ich wnętrzu this
wskazuje na "kliknięty" element, a nie obiekt naszej klasy. Za pomocą bind lub funkcji strzałkowej możemy to zmienić:
this.btnPrev.addEventListener("click", this.slidePrev.bind(this));
//lub
this.btnPrev.addEventListener("click", () => this.slidePrev());
Powyższą metodę odpalmy na końcu konstruktora.
Metody slidePrev() i slideNext()
W powyższej metodzie po kliknięciu na przyciski odpalamy zmianę aktualnego slajdu na poprzedni/następny za pomocą metod slidePrev() i slideNext():
class Slider {
...
slidePrev() {
this.currentSlide--;
if (this.currentSlide < 0) {
this.currentSlide = this.slides.length - 1;
}
this.setSlide(this.currentSlide);
}
slideNext() {
this.currentSlide++;
if (this.currentSlide > this.slides.length - 1) {
this.currentSlide = 0;
}
this.setSlide(this.currentSlide);
}
}
Metody tylko wyliczają odpowiedni index slajdu. Do samej zmiany slajdu posłuży nam oddzielna metoda.
Metoda setSlide()
Przełączanie slajdu polegać będzie na usunięciu klasy is-active z aktywnego slajdu, a następnie ustawieniu jej następnemu slajdowi:
class Slider {
constructor() {
...
this.setSlide(this.currentSlide);
}
setSlide(index) {
//usuwamy klasę is-active wszystkim slajdom
this.slides.forEach(slide => {
slide.classList.remove("is-active");
});
//dodajemy ją tylko wybranemu
this.slides[index].classList.add("is-active");
//aktualny slide przestawiamy na wybrany
this.currentSlide = index;
}
}
Przy okazji odpalmy tą metodę w konstruktorze (linia 4), dzięki czemu po uruchomieniu slajdera ustawi on sobie automatycznie odpowiedni slajd na aktywny.
Metoda createPagination()
Zmiany slajdów mamy kompletne. Kolejną czynnością jest stworzenie metody generującej paginację zmieniającą slajdy:
class Slider {
constructor() {
...
this.createPagination();
}
createPagination() {
this.dots = document.createElement("div");
this.dots.classList.add("slider-pagination");
//tworzymy pętlę w ilości liczby slajdów
this.slides.forEach((el, i) => {
const btn = document.createElement("button");
btn.classList.add("slider-pagination-button");
btn.type = "button";
btn.innerText = i + 1;
btn.addEventListener("click", () => this.setSlide(i));
this.dots.append(btn);
});
this.slider.append(this.dots);
}
}
Tak samo jak poprzednio odpalmy ją też od razu w konstruktorze.
Przy zmianie slajdu powinniśmy ustawić na aktywną odpowiednią kropkę paginacji. Stwórzmy do tego oddzielną metodę:
class Slider {
...
setSlide(index) {
this.slides.forEach(slide => {
slide.classList.remove("is-active");
});
this.slides[index].classList.add("is-active");
const dots = [...this.dots.children];
dots.forEach(dot => dot.classList.remove("is-active"));
dots[index].classList.add("is-active");
this.currentSlide = index;
}
...
}
Automatyzacja
Dodajmy do naszego slidera automatyczne przełączanie slajdów. Wykorzystamy tutaj setTimeout(). Wystarczy za pomocą tej funkcji wywołać powyższą metodę slideNext()
.
class Slider {
constructor() {
...
this.time = null;
...
}
...
setSlide(index) {
...
clearTimeout(this.time);
this.time = setTimeout(() => this.slideNext(), 6000); //co 6 sekund automatycznie się przełączy
}
}
Gdy użytkownik najedzie kursorem na slider, możemy założyć, że może chce się dokładniej przyjrzeć, albo na spokojnie przeczytać co tam się znajduje (ale przecież wiemy, że tak na prawdę chce się delektować naszym dziełem). Na ten czas przydało by się wyłączyć automatyczne przewijanie. Posłuży nam do tego nowa metoda, którą odpalimy na końcu konstruktora:
class Slider {
constructor() {
...
this.handleMouseEnterAndOut();
}
handleMouseEnterAndOut() {
this.slider.addEventListener("mouseenter", () => clearTimeout(this.time));
this.slider.addEventListener("mouseout", () => {
clearTimeout(this.time);
this.time = setTimeout(() => this.slideNext(), 6000);
})
}
}
Opcje
W powyższym kodzie przełączanie slajdów jest ustawione na sztywno 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 naszej klasy trafi obiekt z opcjami, które ustawi programista. Opcje te nadpiszą domyślne ustawienia, w wyniku czego dostaniemy nowy scalony obiekt z ustawieniami slidera.
//to co sobie ustawi programista
const opts = {
width : 666
}
//domyślne ustawienia
const defaultOpts = {
width: 10,
height: 200
}
//scalony obiekt
console.log({ ...defaultOptions, ...opts }); //{width: 666, height:200}
lub
const opts = {
width : 666
}
const defaultOptions = {
width: 10,
height: 200
}
console.log(Object.assign({}, defaultOptions, opts)); //{width: 666, height:200}
Dodajmy domyślne ustawienia w konstruktorze:
class Slider {
constructor(query, opts) {
this.slider = document.querySelector(query); //element slider
this.slides = [...this.slider.querySelectorAll(".slider-slide")];
this.time = null; //tutaj będziemy podczepiać setTimeout
this.currentSlide = Math.max(0, this.slides.findIndex(el => el.classList.contains("is-active"))); //aktualny slide
const defaultOpts = {
pauseTime: 0
};
this.options = {...defaultOpts, ...opts};
this.createPrevNext();
this.createPagination();
this.setSlide(this.currentSlide);
}
...
}
Domyślnie czas przerwy między kolejnym przełączaniem będzie wynosił 0. Jeżeli programista tego nie zmieni, nie będziemy przełączać automatycznie slajdów:
class Slider {
constructor() {
this.slider = document.querySelector(query); //element slider
this.slides = [...this.slider.querySelectorAll(".slider-slide")];
this.time = null; //tutaj będziemy podczepiać setTimeout
this.currentSlide = Math.max(0, this.slides.findIndex(el => el.classList.contains("is-active"))); //aktualny slide
const defaultOpts = {
pauseTime: 0
};
this.options = {...defaultOpts, ...opts};
this.autoChangeSlides = typeof this.options.pauseTime === "number" && this.options.pauseTime > 0;
this.createPrevNext();
this.createPagination();
if (this.autoChangeSlides) this.handleMouseEnter();
this.setSlide(this.currentSlide);
}
setSlide(index) {
this.slides.forEach(slide => {
slide.classList.remove("is-active");
});
this.slides[index].classList.add("is-active");
const dots = [...this.dots.children];
dots.forEach(dot => dot.classList.remove("is-active"));
dots[index].classList.add("is-active");
this.currentSlide = index;
if (this.autoChangeSlides) {
clearTimeout(this.time);
this.time = setTimeout(() => this.slideNext(), this.options.pauseTime)
}
}
...
}
Podobne opcje możemy też ustawić dla innych rzeczy - np. generowania przycisków poprzedni/następny czy też paginacji:
class Slider {
constructor(query, opts) {
this.slider = document.querySelector(query); //element slider
this.slides = [...this.slider.querySelectorAll(".slider-slide")];
this.time = null;
this.currentSlide = Math.max(0, this.slides.findIndex(el => el.classList.contains("is-active")));
const defaultOpts = {
pauseTime: 0,
generateDots: true,
generatePrevNext: true
};
this.options = {...defaultOpts, ...opts};
this.autoChangeSlides = typeof this.options.pauseTime === "number" && this.options.pauseTime > 0;
if (this.options.generatePrevNext) this.createPrevNext();
if (this.options.generateDots) this.createPagination();
if (this.autoChangeSlides) {
this.handleMouseEnter();
}
this.setSlide(this.currentSlide);
}
slidePrev() {
this.currentSlide--;
if (this.currentSlide < 0) {
this.currentSlide = this.slides.length - 1;
}
this.setSlide(this.currentSlide);
}
slideNext() {
this.currentSlide++;
if (this.currentSlide > this.slides.length - 1) {
this.currentSlide = 0;
}
this.setSlide(this.currentSlide);
}
setSlide(index) {
this.slides.forEach(slide => {
slide.classList.remove("is-active");
});
this.slides[index].classList.add("is-active");
if (this.options.generateDots) {
const dots = [...this.dots.children];
dots.forEach(dot => dot.classList.remove("is-active"));
dots[index].classList.add("is-active");
}
this.currentSlide = index;
if (this.autoChangeSlides) {
clearTimeout(this.time);
this.time = setTimeout(() => this.slideNext(), this.options.pauseTime)
}
}
createPrevNext() {
this.btnPrev = document.createElement("button");
this.btnPrev.type = "button";
this.btnPrev.innerText = "Poprzedni slajd";
this.btnPrev.classList.add("slider-button", "slider-button-prev");
this.btnPrev.addEventListener("click", this.slidePrev.bind(this));
this.btnNext = document.createElement("button");
this.btnNext.type = "button";
this.btnNext.innerText = "Następny slajd"
this.btnNext.classList.add("slider-button", "slider-button-next");
this.btnNext.addEventListener("click", this.slideNext.bind(this));
const nav = document.createElement("div");
nav.classList.add("slider-nav");
this.slider.append(this.btnPrev);
this.slider.append(this.btnNext);
this.slider.append(nav);
}
createPagination() {
this.dots = document.createElement("div");
this.dots.classList.add("slider-pagination");
this.slides.forEach((el, i) => {
const btn = document.createElement("button");
btn.classList.add("slider-pagination-button");
btn.type = "button";
btn.innerText = i + 1;
btn.addEventListener("click", () => this.setSlide(i));
this.dots.append(btn);
});
this.slider.append(this.dots);
}
handleMouseEnter() {
this.slider.addEventListener("mouseenter", () => clearTimeout(this.time));
this.slider.addEventListener("mouseout", () => {
if (this.autoChangeSlides) {
this.time = setTimeout(() => this.slideNext(), this.options.pauseTime)
}
})
}
}
Demo
Aby użyć naszego slidera, skorzystamy z kodu:
//automatyczny slider bez paginacji i strzałek
const options1 = {
pauseTime : 3000,
}
const slider1 = new Slider("#slider1", options1);
//manualny slider z przyciskami
const options2 = {
pauseTime : 0,
generatePrevNext : false,
generatePagination : false
}
const slide2 = new Slider("#slider2", options2);
Demo
Trening czyni mistrza
Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania