Slider/Caroussel Vanilla JS használatával

Mi a cél?

Egy olyan kép carousselt készítünk, mely automatikusan váltogatja a képeket, de lehetőségünk van az egyéni lapozásra is vagy a jobbra/balra nyilakkal, vagy pedig a képeket jelképező alsó pontokra való kattintással. Az egyedi lapozást befejezve egy meghatározott idő múlva az automatikus kép caroussel újra indul. A JS modul rendszer gyakorlása végett a képek adatai egy külön JS file-ból töltődnek be és egy függvényen keresztül állíthatjuk a képek váltakozásának gyakoriságát és a képek magasságát.

DEMO (a képre kattintva indul)

Slider/Caroussel

HTML

Mivel nem tudjuk, hogy hány kép fog betöltődni, ezért a változó részek (a mindenkori kép adatai és a képek alján megjelenő, összes képet jelképező pontok) kikommentelve jelennek meg a HTML-ben. Ezek a CSS megírása miatt hasznosak, először az ember megírja a statikus vázat, de ezeket az elemeket a JS fogja legeneráltatni majd a DOM-on keresztül. Szerepel még egy képszámláló is majd a bal felső sarokban, ez a mindekori képet jelzi. A kód alján a JS modulok miatt a script-ben most az src mellett szerepelnie kell a type="module" attributumnak is:

<body> <div class="gallery"> <div class="slider-box"> <!-- <div class="slider"> <div class="count">1 / 5</div> <img src="./images/1.jpg" alt="Opatija"> <div class="caption">Opatija</div> </div> --> </div> <span class="left-arrow">&#10094;</span> <span class="right-arrow">&#10095;</span> </div> <div class="dots"> <!-- <span class="dot"></span> <span class="dot"></span> <span class="dot"></span> <span class="dot"></span> <span class="dot"></span> --> </div> <script type="module" src="./js/app.js"></script> </body>

A stílus

A szokásos boilerplate kód után

* { margin: 0; padding: 0; } *, *::before, *::after { box-sizing: inherit; } html { box-sizing: border-box; } body { min-height: 100vh; font-family: sans-serif; background-color: rgb(169,169,169); }

a képet befogadó parents div-re (.slider) ráírjuk a position: relative; tulajdonságot, hogy a caption-t és a count-et ehhez képest tudjuk abszolut pozicionálni:

.gallery { position: relative; display: flex; } .slider { height: 90vh; width: 100vw; text-align: center; overflow: hidden; position: relative; }

A képek váltogatását a szokásos display block és none párossal oldjuk meg egy egyszerű animáció kiséretében:

.show { display: block; animation-name: show-in; animation-duration: 2s; } .hide { display: none; } @keyframes show-in { 0% { opacity: 0; } 100% { opacity: 1; } }

Soha ne feledjük a képet a szülő elemhez képest valamelyik irányban kifeszíteni, mert egyedül ez garantálja csak, hogy a kép a befoglaló elemen belül marad:

img { width: 100%; }

A kép feliratot, a kép számolást és a nyilakat abszolút pozicionálással állítjuk a megfelelő helyre.

.caption { position: absolute; bottom: 2rem; left: 50%; transform: translate(-50%); background-color: rgba(0,0,0,.4); color: #ffffff; padding: 0.5rem 1.5rem; font-size: 1.5rem; } .count { position: absolute; top: 0; padding: 1rem; font-size: 1.2rem; color: #ffffff; background-color: rgba(0,0,0,.3); } .left-arrow, .right-arrow { position: absolute; cursor: pointer; padding: 1rem; color: white; font-size: 2rem; line-height: 2rem; font-weight: bold; top: 50%; margin-top: -2rem; transition: 0.6s; } .left-arrow:hover, .right-arrow:hover { background-color: rgba(0,0,0,.6); } .left-arrow { left: 0; border-radius: 0 3px 3px 0; } .right-arrow { right: 0; border-radius: 3px 0 0 3px; }

A végére már csak az alsó pontok pozicionálása marad:

.dots { text-align: center; margin-top: 1rem; } .dot { display: inline-block; width: 1rem; height: 1rem; border-radius: 50%; background-color: #ececec; cursor: pointer; margin-right: 0.7rem; transition: 0.6s; } .dot:hover { background-color: rgba(0,0,0,.3); } .dot-showed { background-color: rgba(0,0,0,.3); }

Pictures.js

A képek adatai egy tömbben vannak, azon belül képenként egy objektumban, mintha egy adatbázisból kaptuk volna meg a lekérdezett adatokat. Mivel csak egy értéket(tömböt) akarunk exportálni, ezért most az export default parancsot használjuk:

const pictures = [ { url: './images/1.jpg', caption: 'Opatija, tengerparti sétány', alt: 'Opatija, tengerparti sétány' }, { url: './images/2.jpg', caption: 'Vrsar, Isztria', alt: 'Vrsar, Isztria' }, { url: './images/3.jpg', caption: 'Kilátás az omisi erődből', alt: 'Kilátás az omisi erődből' }, { url: './images/4.jpg', caption: 'Krka vízesés', alt: 'Krka vízesés' }, { url: './images/5.jpg', caption: 'Omis, kilátás a Mirabellből', alt: 'Omis, kilátás a Mirabellből' }, { url: './images/6.jpg', caption: 'Cesky Krumlov', alt: 'Cesky Krumlov' }, ] export default pictures;

A JS logika

Importáljuk a képek adatait a pictures.js file-ból és azt követően felírjuk azon DOM objektumokat, mellyel később dolgozni fogunk valamint inicializáljuk az egyedi képváltáshoz használt index változót, mely a mindenkori aktuális kép indexét fogja tartalmazni. Az auto kapcsoló változó, azt mutatja, hogy a képek váltakozása egyedi vagy automatikus módon történik.

import pictures from './pictures.js'; const sliderBox = document.querySelector('.slider-box'); const leftArrow = document.querySelector('.left-arrow'); const rightArrow = document.querySelector('.right-arrow'); const dots = document.querySelector('.dots'); let index = 0; let auto = true;

A képek betöltése és a DOM felépítése

A kép adatok alapján először felépítjük a kép konténert .slider, majd alatta a képeket jelképező pontokat. Valamennyi kép hide-olva van és paraméterként kapjuk majd a slider magasságát. Ezt követően az egyes képek adatai (számozás, url és cím) alapján valamennyi képet hozzákapcsolunk a befoglaló szülő elemhez. A képek alá megrajzoljuk az őket jelképező pontokat is. A két eljárást két külön függvényben hajtjuk végre.

function loadPictures() { for (let i = 0; i < pictures.length; i++) { const sliderElement = document.createElement('div'); sliderElement.classList.add('slider', 'hide'); sliderElement.style.height = `${sliderViewportHeight}vh`; sliderElement.innerHTML = ` <div class="count">${i + 1} / ${pictures.length}</div> <img src=${pictures[i].url} alt=${pictures[i].alt}> <div class="caption">${pictures[i].caption}</div> `; sliderBox.append(sliderElement); } } function showDots() { for (let i = 0; i < pictures.length; i++) { const dotElem = document.createElement('span'); dotElem.classList.add('dot'); dots.prepend(dotElem); } }

A képek betöltése után meg is jelenítjük az első képet és pöttyöt.

function showPictureAutoFirst() { slider[index].classList.remove('hide'); slider[index].classList.add('show'); dot[index].classList.add('dot-showed'); }

Az automatikus kép mozgatás

Induláskor automatikusan elindul a kép váltogatás, a paraméterként kapott frequency -nek megfelelően. A megfelelő indexű kép (ez kezdetben 0) először eltűnik, aztán a következő megjelenik. Ha a sor végére érünk, akkor az index számítása újraindul nulláról. A timer-nek kezdetben nincs jelentősége, ez majd akkor kap szerepet, ha egyedileg váltogatjuk a képeket és mérni fogjuk, hogy mióta nem nem történt már semmi változás sem a nyilakon, sem pedig a pontokon keresztül, s ekkor elindítjuk majd az automatikus képváltogatást.

function showPictureAuto() { if (timer > frequency) { dot[index].classList.remove('dot-showed'); slider[index].classList.remove('show') slider[index].classList.add('hide'); index++; if (index >= pictures.length) index = 0; slider[index].classList.remove('hide'); slider[index].classList.add('show'); dot[index].classList.add('dot-showed'); } }

Az egyedi képmozgatás

A nyilakon és a pontokon keresztül átadunk majd egy paramétert (i), hogy melyik képre szeretnénk menni. Az index változó tárolja a jelenlegi kép indexét, ezt tehát először el kell tüntetnünk és utána az i indexű képet meg kell jelenítenünk. A nyilaknál a két végpontot egyedileg kell kezelnünk.

function showPicture(i) { dot[index].classList.remove('dot-showed'); slider[index].classList.remove('show') slider[index].classList.add('hide'); if (i >= pictures.length) i = 0; if (i < 0) i = pictures.length - 1; slider[i].classList.remove('hide'); slider[i].classList.add('show'); dot[i].classList.add('dot-showed'); index = i; }

Az eseményfigyelők

Mindkét nyilra és a pöttyökre is felírjuk az eseményfigyelőket. Ha egyedi kattintás történik akkor átállítjuk az automatikus figyelőt false-ra, lenullázzuk a számlálót, töröljük az automatikus képmegjelenítést és a kívánt indexü képhez navigálunk.

leftArrow.addEventListener('click', () => { auto = false; timer = 0; clearInterval(showAuto); showPicture(index - 1); }); rightArrow.addEventListener('click', () => { auto = false; timer = 0; clearInterval(showAuto); showPicture(index + 1); }); for (let i = 0; i < pictures.length; i++) { dot[i].addEventListener('click', () => { auto = false; timer = 0; clearInterval(showAuto); showPicture(i); }); }

A teljes függvény

Az egyedi paraméterezhetőség miatt az egész folyamatot becsomagoljuk egy függvénybe. Ezen belül elkezdjük a folyamatot sorban. A timer nagyobb legyen mint a frequency, hogy automatikusan induljunk, betöltjük a képeket, felírjuk az éppen elkészült .slider elemek NodeList-jét, hogy a következőkben tudjunk velük dolgozni, felírjuk a pöttyöket, az éppen elkészült pöttyök NodeList-jét, megjelenítjük az első képet és pöttyöket, és elindítjuk az automatikus kép váltást egy setInterval függvénnyel. Az egyedi és automatikus közti átmenet vizsgálatára felírunk még egy setInterval függvényt.

function sliderIndividualAndAutomatic(frequency, sliderViewportHeight) { let timer = frequency + 1000; loadPictures(); const slider = document.querySelectorAll('.slider'); showDots(); const dot = document.querySelectorAll('.dot'); showPictureAutoFirst(); const showAuto = setInterval(showPictureAuto, frequency); setInterval(() => { if (!auto) { timer += 1000; showPictureAuto(); } }, frequency)

Ezen a függvényen belül helyezkedik el az összes korábbi függvény és eseményfigyelő is.

A végén pedig két tetszőleges értékkel meghívjuk a kép váltogató függvényünket.

sliderIndividualAndAutomatic(3000, 90)

A teljes kód itt található.