A web design and development studio

Carousels, slid­ers, and gal­leries come up again and again in front-end web devel­op­ment. We recent­ly put togeth­er a sequence of review videos in an infi­nite­ly scrolling gallery for JDS Labs, a head­phone amp man­u­fac­tur­er and one of our clients. In this arti­cle, I’ll show you how we did it. By the end, we’ll have some­thing like this: 

Infi­nite slides gallery using vanil­la JS@thiagorossenerCode­Pen

Before we get start­ed, let’s go through our objectives:

Our goal is to main­tain con­ti­nu­ity when click­ing on the for­ward and back­ward but­tons. In order to accom­plish this, we will need to hide two more slides on the left and right sides, in addi­tion to the three slides that are imme­di­ate­ly vis­i­ble in the exam­ple above.

If more slides are need­ed, they should be added to the right side. This is because we are using the :nth-child CSS selec­tor to help style our carousel. There’ll be more about this later.

When click­ing on the pre­vi­ous slide, the last hid­den slide should become the first in the sequence. 

When click­ing on the next slide, the first hid­den slide should go to the last posi­tion of the list.

As a ref­er­ence, here is a schema illus­trat­ing the sequenc­ing of the slides. 

Our struc­ture

The HTML itself is straight­for­ward. We’re going to set the images of our carousel using indi­vid­ual CSS class­es and the background-image prop­er­ty (as opposed to using img tags). This way we can more eas­i­ly con­trol the aspect ratio of the images.

<pre class=“language-html” data-children-count=“0”><div class=“gallery”> <div class=“gallery__prev”></div> <div class=“gallery__next”></div> <div class=“gallery__stream”> <div class=“gallery__item bg‑1”></div> <div class=“gallery__item bg‑2”></div> <div class=“gallery__item bg‑3”></div> <div class=“gallery__item bg‑4”></div> <div class=“gallery__item bg‑5”></div> <div class=“gallery__item bg‑6”></div> <div class=“gallery__item bg‑7”></div> </div></div></pre>

Stylin’

First, let’s style the gallery and its items. Notice that the ele­ments are cen­tered vertically.

<pre class=“language-css” data-children-count=“0”>* { mar­gin: 0; padding: 0; box-siz­ing: border-box;}body { back­ground-col­or: #000;}/* Gallery */.gallery { posi­tion: absolute; top: 50%; trans­form: translateY(-50%); width: 100%; height: 90%; max-height: 28vw; over­flow: hidden;}.gallery__stream { posi­tion: rel­a­tive; top: 50%; trans­form: translateY(-50%); width: 100%; height: 100%;}.gallery__item { posi­tion: absolute; width: 50%; height: 100%; tran­si­tion: 1s all ease; back­ground-size: cov­er; back­ground-repeat: no-repeat; back­ground-posi­tion: cen­ter cen­ter; bor­der-radius: 5px;}</pre>

Pay atten­tion to the transition prop­er­ty on the .gallery__item. This lit­tle guy will pro­vide the silky smooth tran­si­tion from slide to slide. 

Now, we need to posi­tion each gallery item, con­sid­er­ing its Z axis posi­tion. This ensures that the slides don’t over­lap incor­rect­ly. In order to accom­plish this, we are going to use the :nth-child selec­tor. This selec­tor allows us to specif­i­cal­ly tar­get indi­vid­ual child ele­ments with­in their par­ent — in our case we will tar­get each of our vis­i­ble slides as well as the remain­ing off-screen” slides.

<pre class=“language-css” data-children-count=“0”>.gallery__item:nth-child(1) { left: 0; z‑index: 1; trans­form: translateX(-100%) scale(.8);}.gallery__item:nth-child(2) { left: 0; z‑index: 2; trans­form: translateX(-50%) scale(.8);}.gallery__item:nth-child(3) { left: 50%; z‑index: 4; trans­form: translateX(-50%) scale(1);}.gallery__item:nth-child(4) { left: 100%; z‑index: 2; trans­form: translateX(-50%) scale(.8);}.gallery__item:nth-child(n+5) { left: 100%; z‑index: 1; trans­form: scale(.8);}</pre>

Since we haven’t added the images, noth­ing dis­plays yet. Let’s cre­ate the back­ground classes:

<pre class=“language-css” data-children-count=“0”>.bg‑1 { back­ground-image: url(“

);}.bg‑2” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

);}.bg‑3” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

);}.bg‑4” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

);}.bg‑5” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

);}.bg‑6” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

);}.bg‑7” class=“redactor-autoparser-object”>https://res.cloudinary.com/dm7… { back­ground-image: url(“

”);}</​pre>

Neat! Anakin is front and cen­ter, ready for some action.

On to the buttons…

To cre­ate the next and pre­vi­ous but­tons, we will style them exact­ly like the vis­i­ble slides on the left and ride sides of the frame. 

We also need to posi­tion them a lay­er above these slides with a trans­par­ent back­ground. To the user, it appears as if they are click­ing on the slide itself when real­ly they are click­ing the buttons. 

<pre class=“language-css” data-children-count=“0”>.gallery__prev,.gallery__next { posi­tion: absolute; top: 50%; z‑index: 4; width: 50%; height: 100%; trans­form: translateX(-50%) translateY(-50%) scale(.8); cur­sor: pointer;}.gallery__prev { left: 0;}.gallery__next { left: 100%;}</pre>

To pol­ish the appear­ance, let’s add a gra­di­ent to the gallery sides using the pseu­do ele­ments :before and :after

Note: Be aware of the z‑index! We need to posi­tion the gra­di­ents so that the but­tons remain clickable. 

<pre class=“language-css” data-children-count=“0”>.gallery:before,.gallery:after { dis­play: block; con­tent: “”; posi­tion: absolute; top: 0; width: 20%; height: 100%; z‑index: 3;}.gallery:before { left: 0; back­ground: linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);}.gallery:after { right: 0; back­ground: linear-gradient(to left, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);}</pre>

Mak­ing it work

The hard part is done. Now, let’s put it all togeth­er. Before adding our code, we want to ensure that all of our HTML is loaded. There are a num­ber of ways of doing this, but let’s use the DOMContentLoad event

<pre class=“language-js” data-children-count=“0”>document.addEventListener(‘DOMContentLoaded’, func­tion() { // Code comes here});</pre>

Then we select the ele­ments we need:

<pre class=“language-js” data-children-count=“0”>document.addEventListener(‘DOMContentLoaded’, func­tion() { var stream = document.querySelector(‘.gallery__stream’); var items = document.querySelectorAll(‘.gallery__item’); var prev = document.querySelector(‘.gallery__prev’); var next = document.querySelector(‘.gallery__next’);});</pre>

And bind the click events to the but­tons prev and next:

<pre class=“language-js”>document.addEventListener(‘DOMContentLoaded’, func­tion() { var stream = document.querySelector(‘.gallery__stream’); var items = document.querySelectorAll(‘.gallery__item’); var prev = document.querySelector(‘.gallery__prev’); var next = document.querySelector(‘.gallery__next’); prev.addEventListener(‘click’, func­tion() { stream.insertBefore(items[items.length — 1], items[0]); items = document.querySelectorAll(‘.gallery__item’); }); next.addEventListener(‘click’, func­tion() { stream.appendChild(items[0]); items = document.querySelectorAll(‘.gallery__item’); });});</​pre>

Atten­tion! Notice that we update the items vari­able. This is extreme­ly impor­tant because once we mod­i­fy the item’s posi­tion in the DOM, the vari­able needs to be updat­ed as well. Oth­er­wise we will select the wrong item on the next click.

As you’ve seen, it’s easy to get a gallery up and run­ning. This code does have some lim­i­ta­tions though, like requir­ing at least 5 slides in order to work properly.

There are also improve­ments that could be made, as needed. 

  • Using a lib to lazy load the images not vis­i­ble in the screen
  • Avoid­ing mul­ti­ple, sequen­tial user clicks, allow­ing the slides to fin­ish their tran­si­tion first
  • Adding indi­ca­tors to the slides

How­ev­er, let’s keep this list in mind for a sub­se­quent post. I’m tired!

Until next time. Bye.

If you made it this far, you may also like

What is going on here folks?

Curious_zombie

Your guess is as good as ours.


Feral © 2019.