Carrossel de Imagens
Exibe uma sequência de imagens navegáveis com legendas. O aprendiz avança pelos slides no próprio ritmo, ideal para conteúdo visual como processos, exemplos e antes/depois.
Experimente
Fases da Lua
1/8Código-fonte
Dados de exemplo
Cada slide tem imagem, título e descrição. A ordem do array é a ordem de navegação. Quatro a oito slides é a faixa ideal — menos que isso não justifica o formato, mais que isso cansa quem navega um por um.
| 1 |
<?php
|
| 2 |
$titulo = 'Metamorfose da Borboleta';
|
| 3 |
$slides = [
|
| 4 |
['imagem' => 'ovo.jpg', 'titulo' => '1. Ovo', 'descricao' => 'A fêmea deposita o ovo na face inferior de uma folha, escolhida por já ser o alimento da lagarta que vai eclodir dali.'],
|
| 5 |
['imagem' => 'lagarta.jpg', 'titulo' => '2. Lagarta', 'descricao' => 'Fase de crescimento: a lagarta come continuamente e troca de pele várias vezes conforme o corpo aumenta de tamanho.'],
|
| 6 |
['imagem' => 'crisalida.jpg', 'titulo' => '3. Crisálida', 'descricao' => 'Dentro do invólucro rígido, o corpo da lagarta se reorganiza quase por completo antes de emergir transformado.'],
|
| 7 |
['imagem' => 'borboleta.jpg', 'titulo' => '4. Borboleta', 'descricao' => 'O adulto emerge, espera as asas secarem e endurecerem, e só então voa pela primeira vez.'],
|
| 8 |
];
|
| 9 |
$total = count($slides);
|
| 10 |
?>
|
Cabeçalho
Título e indicador de posição ("1/4") ficam juntos, centralizados — dá pra saber onde se está na sequência antes mesmo de olhar o slide.
| 1 |
<div class="carrossel">
|
| 2 |
<div class="cabecalho">
|
| 3 |
<p class="titulo"><?= htmlspecialchars($titulo) ?></p>
|
| 4 |
<span class="indicador" data-indicador>1/<?= $total ?></span>
|
| 5 |
</div>
|
Slides
Todos os slides já existem no HTML desde o carregamento — só o primeiro nasce com a classe "ativo". Trocar de slide é só mover essa classe, nunca recriar ou apagar conteúdo.
| 1 |
<div class="slides">
|
| 2 |
<?php foreach ($slides as $i => $slide): ?>
|
| 3 |
<div class="slide<?= $i === 0 ? ' ativo' : '' ?>" data-slide="<?= $i ?>">
|
| 4 |
<img class="imagem" src="<?= htmlspecialchars($slide['imagem']) ?>" alt="<?= htmlspecialchars($slide['titulo']) ?>">
|
| 5 |
<p class="numero-titulo"><?= htmlspecialchars($slide['titulo']) ?></p>
|
| 6 |
<p class="descricao"><?= htmlspecialchars($slide['descricao']) ?></p>
|
| 7 |
</div>
|
| 8 |
<?php endforeach; ?>
|
| 9 |
</div>
|
Navegação
"Anterior"/"Próximo" desabilitam nas pontas — a navegação não dá a volta do último slide pro primeiro. Os dots não têm essa restrição: pulam direto pra qualquer slide, de qualquer ponto da sequência.
| 1 |
<div class="navegacao">
|
| 2 |
<button type="button" class="botao-anterior" disabled>← Anterior</button>
|
| 3 |
<div class="dots">
|
| 4 |
<?php for ($i = 0; $i < $total; $i++): ?>
|
| 5 |
<button type="button" class="dot<?= $i === 0 ? ' ativo' : '' ?>" data-dot="<?= $i ?>"></button>
|
| 6 |
<?php endfor; ?>
|
| 7 |
</div>
|
| 8 |
<button type="button" class="botao-proximo">Próximo →</button>
|
| 9 |
</div>
|
| 10 |
</div>
|
Cartão e cabeçalho
O cabeçalho tem fundo próprio (mais escuro que o cartão) só pra funcionar como caixa de título — separa visualmente "onde estou" (título + indicador) do conteúdo que muda a cada slide.
| 1 |
.carrossel {
|
| 2 |
border: 1px solid #e2e8f0;
|
| 3 |
border-radius: 16px;
|
| 4 |
overflow: hidden;
|
| 5 |
font-family: inherit;
|
| 6 |
}
|
| 7 |
.cabecalho {
|
| 8 |
padding: 20px 24px;
|
| 9 |
text-align: center;
|
| 10 |
background: #f1f5f9;
|
| 11 |
border-bottom: 1px solid #cbd5e1;
|
| 12 |
}
|
| 13 |
.titulo {
|
| 14 |
margin: 0;
|
| 15 |
font-size: 18px;
|
| 16 |
font-weight: 700;
|
| 17 |
color: #0f172a;
|
| 18 |
}
|
| 19 |
.indicador {
|
| 20 |
display: inline-block;
|
| 21 |
margin-top: 6px;
|
| 22 |
font-size: 12px;
|
| 23 |
font-family: monospace;
|
| 24 |
color: #64748b;
|
| 25 |
}
|
Slide
display:none no slide inativo, não opacity nem visibility — um slide escondido não deve ocupar espaço nem ser lido por leitor de tela enquanto não é a vez dele.
| 1 |
.slide {
|
| 2 |
display: none;
|
| 3 |
padding: 20px;
|
| 4 |
}
|
| 5 |
.slide.ativo {
|
| 6 |
display: block;
|
| 7 |
}
|
| 8 |
.imagem {
|
| 9 |
width: 100%;
|
| 10 |
aspect-ratio: 1 / 1;
|
| 11 |
object-fit: cover;
|
| 12 |
border-radius: 12px;
|
| 13 |
margin-bottom: 16px;
|
| 14 |
}
|
| 15 |
.numero-titulo {
|
| 16 |
margin: 0 0 8px;
|
| 17 |
font-size: 16px;
|
| 18 |
font-weight: 600;
|
| 19 |
color: #1e293b;
|
| 20 |
}
|
| 21 |
.descricao {
|
| 22 |
margin: 0;
|
| 23 |
font-size: 14px;
|
| 24 |
line-height: 1.6;
|
| 25 |
color: #475569;
|
| 26 |
}
|
Navegação
O dot ativo fica maior e colorido (não é só uma troca de cor) — o formato diferente ajuda quem tem dificuldade de distinguir cor a perceber a posição atual mesmo assim.
| 1 |
.navegacao {
|
| 2 |
display: flex;
|
| 3 |
align-items: center;
|
| 4 |
gap: 12px;
|
| 5 |
padding: 0 20px 20px;
|
| 6 |
}
|
| 7 |
.botao-anterior,
|
| 8 |
.botao-proximo {
|
| 9 |
border: 1px solid #cbd5e1;
|
| 10 |
border-radius: 8px;
|
| 11 |
background: #f8fafc;
|
| 12 |
padding: 8px 14px;
|
| 13 |
font-size: 13px;
|
| 14 |
font-weight: 600;
|
| 15 |
color: #334155;
|
| 16 |
cursor: pointer;
|
| 17 |
}
|
| 18 |
.botao-anterior:disabled,
|
| 19 |
.botao-proximo:disabled {
|
| 20 |
opacity: 0.4;
|
| 21 |
cursor: default;
|
| 22 |
}
|
| 23 |
.dots {
|
| 24 |
flex: 1;
|
| 25 |
display: flex;
|
| 26 |
justify-content: center;
|
| 27 |
gap: 6px;
|
| 28 |
}
|
| 29 |
.dot {
|
| 30 |
width: 8px;
|
| 31 |
height: 8px;
|
| 32 |
border-radius: 999px;
|
| 33 |
border: none;
|
| 34 |
background: #cbd5e1;
|
| 35 |
cursor: pointer;
|
| 36 |
transition: width 0.15s ease, background-color 0.15s ease;
|
| 37 |
}
|
| 38 |
.dot.ativo {
|
| 39 |
width: 20px;
|
| 40 |
background: #6366f1;
|
| 41 |
}
|
Ir para um slide
Troca o slide e o dot ativos juntos, sempre pelo mesmo caminho — clique num dot, clique em "Próximo" ou em "Anterior" chamam esta mesma função, então nunca ficam fora de sincronia um do outro.
| 1 |
document.addEventListener('DOMContentLoaded', function () {
|
| 2 |
var carrossel = document.querySelector('.carrossel');
|
| 3 |
var slides = carrossel.querySelectorAll('.slide');
|
| 4 |
var dots = carrossel.querySelectorAll('.dot');
|
| 5 |
var indicador = carrossel.querySelector('.indicador');
|
| 6 |
var btnAnterior = carrossel.querySelector('.botao-anterior');
|
| 7 |
var btnProximo = carrossel.querySelector('.botao-proximo');
|
| 8 |
var total = slides.length;
|
| 9 |
var atual = 0;
|
| 10 |
|
| 11 |
function irPara(indice) {
|
| 12 |
slides[atual].classList.remove('ativo');
|
| 13 |
dots[atual].classList.remove('ativo');
|
| 14 |
|
| 15 |
atual = indice;
|
| 16 |
|
| 17 |
slides[atual].classList.add('ativo');
|
| 18 |
dots[atual].classList.add('ativo');
|
| 19 |
indicador.textContent = (atual + 1) + '/' + total;
|
| 20 |
btnAnterior.disabled = atual === 0;
|
| 21 |
btnProximo.disabled = atual === total - 1;
|
| 22 |
}
|
Ligar os controles
"Anterior"/"Próximo" andam um slide por vez e nunca dão a volta (por isso desabilitam nas pontas); os dots pulam direto pro índice clicado, de qualquer slide pra qualquer outro.
| 1 |
btnAnterior.addEventListener('click', function () { irPara(atual - 1); });
|
| 2 |
btnProximo.addEventListener('click', function () { irPara(atual + 1); });
|
| 3 |
dots.forEach(function (dot, indice) {
|
| 4 |
dot.addEventListener('click', function () { irPara(indice); });
|
| 5 |
});
|