Pular para o conteúdo
murad.guide

As Três Camadas

Capítulo 21

Galeria e bibliotecas

4 min de leitura 700 palavras 1 gravura

A Oficina do Farol tem doze imagens ilustrativas de estudo: cerâmica, xilo, farol, mar. Mostrar as doze ao mesmo tempo cansa. A galeria mostra uma. CSS esconde o resto. JavaScript troca a classe .ativa. Depois você reusa a mesma ideia com desliza.js, um arquivo local com licença MIT na pasta. Sem CDN. Sem biblioteca na rede.

O exemplo está em exemplos/21-galeria/. Abra index.html para a versão feita à mão. Abra com-biblioteca.html para a versão com desliza.js.

Trabalhe na cópia inicio desta etapa. Leia e digite o código indicado; mude apenas uma coisa. Antes de recarregar, escreva o resultado que espera. Depois compare com a referência completa ao lado. Se algo falhar, confira primeiro o arquivo salvo e o caminho aberto.

O que você vai aprender

CSS esconde, a classe mostra

.foto {
  display: none;
  max-width: 100%;
  height: auto;
}
.foto.ativa {
  display: block;
}

display: none tira o elemento do fluxo: ele não ocupa espaço. Quem tem .ativa volta a aparecer. Só uma foto deve ter essa classe no HTML inicial.

<img src="imagens/01.png" alt="Foto ilustrativa 1 da Oficina do Farol" class="foto ativa" width="900" height="560">
<img src="imagens/02.png" alt="Foto ilustrativa 2 da Oficina do Farol" class="foto" width="900" height="560">

Os botões:

<button type="button" class="anterior" disabled>Anterior</button>
<button type="button" class="proxima">Próxima</button>

Na primeira foto, Anterior já nasce disabled. Não há para onde voltar.

Erro comum: usar visibility: hidden achando que é o mesmo. O buraco da imagem continua lá. Nesta galeria, display: none é o combinado.

Uma foto visível à mão; a mesma galeria com desliza.js local

Anterior e próxima

let atual = 0;
const fotos = document.querySelectorAll(".foto");
const anterior = document.querySelector(".anterior");
const proxima = document.querySelector(".proxima");

function ir(proximo) {
  fotos[atual].classList.remove("ativa");
  atual = proximo;
  fotos[atual].classList.add("ativa");
  anterior.disabled = atual === 0;
  proxima.disabled = atual === fotos.length - 1;
}

anterior.addEventListener("click", () => ir(atual - 1));
proxima.addEventListener("click", () => ir(atual + 1));

atual é o índice, começando em 0. Você tira .ativa da foto velha, põe na nova, e atualiza disabled. Na última, Próxima apaga. Na primeira, Anterior apaga.

Não precisa “proteger” o clique além disso: botão desabilitado não dispara o fluxo útil. Mesmo assim, não chame ir(-1) à mão.

Pare e experimente. Clique Próxima até o fim. O botão deve esfriar. Recarregue. Anterior deve nascer inútil, Próxima útil.

A mesma ideia com desliza.js local

Uma biblioteca é código de outra pessoa (ou do volume) que você chama, em vez de reescrever o carrossel. Neste material a biblioteca está na pasta, arquivo desliza.js, licença MIT em LICENCA-desliza.txt. Não cole um endereço de CDN. Sem rede, a galeria continua abrindo.

<div id="carrossel">
  <figure data-slide>
    <img src="imagens/01.png" alt="Foto ilustrativa 1">
  </figure>
  <figure data-slide hidden>
    <img src="imagens/02.png" alt="Foto ilustrativa 2">
  </figure>
  <p>
    <button type="button" data-prev>Anterior</button>
    <button type="button" data-next>Próxima</button>
  </p>
</div>
<script src="desliza.js"></script>
<script>Desliza("#carrossel");</script>

data-slide marca cada foto. hidden é o HTML que esconde. Desliza("#carrossel") liga os botões data-prev e data-next.

Trecho da biblioteca:

function Desliza(seletor) {
  const raiz = document.querySelector(seletor);
  const slides = [...raiz.querySelectorAll("[data-slide]")];
  let atual = 0;
  function mostrar() {
    slides.forEach((slide, i) => {
      slide.hidden = i !== atual;
    });
  }
  raiz.addEventListener("click", (evento) => {
    if (evento.target.matches("[data-prev]") && atual > 0) atual -= 1;
    if (evento.target.matches("[data-next]") && atual < slides.length - 1) atual += 1;
    mostrar();
  });
  mostrar();
}

É a mesma conta de índice. Só muda quem esconde: atributo hidden em vez de .ativa.

Erro comum: apontar o script para um site externo “porque é mais moderno”. Neste volume a biblioteca viaja com o zip. Se o arquivo sumir, a página não desliza — e você percebe no disco, não na rede.

Animação CSS, sem pedir nada à internet

@keyframes descreve o caminho da animação. O navegador interpola. Não precisa de script de movimento nem de pacote baixado.

@keyframes pulsa {
  from { transform: scale(1); }
  to { transform: scale(1.03); }
}
.foto.ativa {
  display: block;
  animation: pulsa 0.35s ease;
}

Quando a classe .ativa entra, a foto dá um pulso curto. ease começa mais rápido e termina mais manso. Sem infinite, o pulso roda uma vez.

Você pode animar opacidade (from { opacity: 0 }) no lugar da escala. Fique no CSS. Movimento complexo com biblioteca remota fica fora desta etapa.

Atividades

  1. O que acontece se duas fotos tiverem .ativa ao mesmo tempo?
  2. Por que Anterior nasce com disabled?
  3. Onde está a licença de desliza.js e por que ela acompanha o arquivo?
  4. No exemplo feito à mão, troque o alt da terceira foto. Em qual clique a mudança aparece na tela?

Respostas comentadas

  1. As duas ficam visíveis, uma abaixo da outra. O combinado da galeria quebra. O script assume uma só .ativa.
  2. O índice inicial é 0. Não existe foto antes da primeira. Deixar o botão ativo convidaria um ir(-1) sem foto.
  3. Em LICENCA-desliza.txt, na mesma pasta. MIT pede que o aviso viaje com o código. Redistribuir sem o texto da licença descumpre o combinado.
  4. No segundo clique em Próxima (fotos 1 → 2 → 3). O alt não muda o desenho; ele descreve a imagem para quem não a vê.

Referências

O arquivo completo

Referência do exemplo 21-galeria, reproduzida aqui para que você possa conferir sem sair da página.

galeria.js

let atual = 0;
const fotos = document.querySelectorAll(".foto");
const anterior = document.querySelector(".anterior");
const proxima = document.querySelector(".proxima");

function ir(proximo) {
  fotos[atual].classList.remove("ativa");
  atual = proximo;
  fotos[atual].classList.add("ativa");
  anterior.disabled = atual === 0;
  proxima.disabled = atual === fotos.length - 1;
}

anterior.addEventListener("click", () => ir(atual - 1));
proxima.addEventListener("click", () => ir(atual + 1));

galeria.css

body { font-family: "Segoe UI", Tahoma, sans-serif; background: #fffaf3; margin: 0; padding: 1rem; }
.galeria { display: flex; justify-content: center; min-height: 20rem; }
.foto { display: none; max-width: 100%; height: auto; }
.foto.ativa { display: block; }
button { font: inherit; padding: 0.5rem 1rem; margin-right: 0.5rem; }
button:focus { outline: 3px solid #c45c26; }
button:disabled { opacity: 0.5; }