Criando uma galeria de imagens infinita com JavaScript puro

Neste artigo ensino a criar uma galeria de slides bem legal usando poucas linhas de código.

Imagem de capa

Fazer uma galeria de imagens é algo recorrente pra quem trabalha com front-end, essa semana eu tive a oportunidade de criar uma bem maneira e gostaria de compartilhar com vocês.

Ao final deste artigo, você vai ter algo assim:

See the Pen Infinite slides gallery using vanilla JS by Thiago Rossener (@thiagorossener) on CodePen.

Antes de começarmos, vamos entender o que será feito.

A ideia é termos um efeito de continuidade sempre que clicarmos para voltar ou avançar, para isso, precisamos de pelo menos mais um slide escondido do lado esquerdo e mais um do lado direito.

Se houverem mais slides, eles deverão se acumular do lado direito, simplesmente porque é mais fácil trabalhar com o CSS dessa maneira.

Ao clicar para mostrar o slide anterior, o último slide escondido do lado direito deve se tornar o primeiro slide da lista.

Ao clicar para mostrar o próximo slide, o primeiro slide (escondido) deverá ir para a última posição da lista.

Pra você visualizar melhor, criei esse esquema:

Esquema em SVG

Muito bem, agora que está tudo explicado, vamos primeiro criar a estrutura HTML e estilizar esses slides, então adicionar as funcionalidades.

Nossa estrutura

A estrutura do HTML é bem simples, vou utilizar classes para adicionar as imagens como plano de fundo e manter os slides proporcionais.

<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>

Estilizando!

Primeiro vamos estilizar a galeria e seus itens. Note que os elementos estão centralizados verticalmente, fiz isso somente para servir de exemplo.

/* Global */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: #000;
}

/* Galeria */

.gallery {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 100%;
  height: 90%;
  max-height: 28vw;
  overflow: hidden;
}

.gallery__stream {
  position: relative;
  top: 50%;
  transform: translateY(-50%);
  width: 100%;
  height: 100%;
}

.gallery__item {
  position: absolute;
  width: 50%;
  height: 100%;
  transition: 1s all ease;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  border-radius: 5px;
}

Perceba que foi adicionada uma propriedade transition no nosso item da galeria, é esse cara que vai dar esse efeito legal de escala quando trocamos de slide 😄

Agora posicionamos cada item da galeria, considerando também sua posição no eixo Z, e assim a gente evita que slides fiquem sobrepostos de maneira errada.

Para isso vamos usar o :nth-child, o seletor de filhos do CSS em que iremos estilizar os elementos de acordo com a ordem em que eles se apresentam dentro de um elemento pai em comum, mais informações sobre isso você encontra aqui.

/* Itens */

.gallery__item:nth-child(1) {
  left: 0;
  z-index: 1;
  transform: translateX(-100%) scale(.8);
}

.gallery__item:nth-child(2) {
  left: 0;
  z-index: 2;
  transform: translateX(-50%) scale(.8);
}

.gallery__item:nth-child(3) {
  left: 50%;
  z-index: 4;
  transform: translateX(-50%) scale(1);
}

.gallery__item:nth-child(4) {
  left: 100%;
  z-index: 2;
  transform: translateX(-50%) scale(.8);
}

.gallery__item:nth-child(n+5) {
  left: 100%;
  z-index: 1;
  transform: scale(.8);
}

Até aqui nada está sendo exibido, já que ainda não temos nossas imagens, então vamos criar as classes com os backgrounds:

/* Backgrounds */

.bg-1 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487589/star-wars-1_kgt8dr.jpg);
}

.bg-2 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487590/star-wars-2_tgzyxe.jpg);
}

.bg-3 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487591/star-wars-3_bkcmeb.jpg);
}

.bg-4 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487591/star-wars-4_opgyza.jpg);
}

.bg-5 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487591/star-wars-5_ulc9tx.jpg);
}

.bg-6 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487591/star-wars-6_la8whc.jpg);
}

.bg-7 {
  background-image: url(https://res.cloudinary.com/dm7h7e8xj/image/upload/v1554487590/star-wars-7_l3fcor.jpg);
}

Pronto, o Anakin já pintou na tela!

Para fazer nossos botões de próximo e anterior, iremos estilizá-los exatamente como os slides visíveis na tela do lado esquerdo e do lado direito.

Iremos também posicioná-los uma camada acima desses slides, mas com um fundo transparente, assim irá parecer que estamos clicando nos slides, quando na verdade estamos clicando nos botões 😆

/* Controles */

.gallery__prev,
.gallery__next {
  position: absolute;
  top: 50%;
  z-index: 4;
  width: 50%;
  height: 100%;
  transform: translateX(-50%) translateY(-50%) scale(.8);
  cursor: pointer;
}

.gallery__prev {
  left: 0;
}

.gallery__next {
  left: 100%;
}

Finalmente, por uma simples questão de estética, vamos adicionar um efeito de degradê nas laterais da galeria, utilizando os pseudo elementos :before e :after, mais sobre eles aqui.

Nota: Atenção ao z-index aqui! Se esses caras estiverem numa camada acima dos botões, não será possível clicar neles. Por isso, posicionamos esses elementos entre os slides e os botões transparentes.

/* Sombras */

.gallery:before,
.gallery:after {
  display: block;
  content: "";
  position: absolute;
  top: 0;
  width: 20%;
  height: 100%;
  z-index: 3;
}

.gallery:before {
  left: 0;
  background:  linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);
}

.gallery:after {
  right: 0;
  background:  linear-gradient(to left, rgba(0,0,0,1) 0%, rgba(0,0,0,0) 100%);
}

Fazendo funcionar

Agora que a parte mais trabalhosa já foi, vamos fazer funcionar. Primeiro temos de garantir que todos os elementos estão na tela, criando nossas funções após o evento DOMContentLoaded.

document.addEventListener('DOMContentLoaded', function() {
    // O código vem aqui
});

Então selecionamos os elementos que precisamos:

document.addEventListener('DOMContentLoaded', function() {
  var stream = document.querySelector('.gallery__stream');
  var items = document.querySelectorAll('.gallery__item');
  var prev = document.querySelector('.gallery__prev');
  var next = document.querySelector('.gallery__next');
});

E por fim, adicionamos os eventos de clique nos botões prev e next:

document.addEventListener('DOMContentLoaded', function() {
  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', function() {
    stream.insertBefore(items[items.length - 1], items[0]);
    items = document.querySelectorAll('.gallery__item');
  });

  next.addEventListener('click', function() {
    stream.appendChild(items[0]);
    items = document.querySelectorAll('.gallery__item');
  });
});

Atenção! Perceba que atualizamos o valor da variável items, isso é muito importante, pois uma vez que modificamos a posição dos itens no DOM, a variável precisa ter esses itens atualizados na ordem correta ou estaremos selecionando os itens errados no próximo clique.

A ideia deste post foi mostrar como é fácil criar uma galeria bem bacana com poucas linhas de código, obviamente este código possui algumas limitações, como precisar ter no mínimo 5 slides para funcionar corretamente.

Além disso, há uma série de melhorias que poderiam ser implementadas aqui, deixo como sugestão algumas delas para você tentar:

Espero que tenha curtido e até a próxima!