Slideshow acessível em JavaScript puro: a pessoa controla o tempo
Monte um carrossel sem jQuery com botões de verdade, posição anunciada, pausa no foco e no mouse, respeito a menos movimento e sem slide escondido no Tab.
Você está no meio da frase "Frete grátis acima de R$ 199 para" e o banner troca para a foto de outro produto. Volta o olho, espera o carrossel dar a volta, perde de novo. Agora imagine isso com leitor de tela, que começa a ler o slide 2 enquanto o slide 3 já entrou. A resposta curta: slideshow não avança sozinho por padrão; se avançar, tem botão de pausa visível e para quando o mouse ou o foco estão nele, e os slides escondidos ficam fora do Tab e do leitor de tela. Mensagem importante não vai para carrossel nenhum.
O exemplo deste guia é a galeria de fotos da página de produto de uma loja de móveis: quatro fotos de uma cadeira, com anterior, próximo, posição "Foto 2 de 4" e, porque o marketing insistiu, avanço automático opcional que respeita quem lê.
Por que o carrossel automático atrapalha
Movimento puxa a atenção. É reflexo, não escolha, e é por isso que o banner que troca sozinho compete com o texto ao lado. Para quem lê devagar, tem dislexia, déficit de atenção ou baixa visão com zoom alto, a troca no meio da leitura apaga o que estava sendo lido.
As diretrizes WCAG tratam isso no critério "pausar, parar, ocultar": conteúdo que se move sozinho, dura mais de cinco segundos e aparece junto com outro conteúdo precisa de um jeito de ser pausado. O slideshow automático típico cai exatamente aí.
Há ainda o problema de alcance. Quem não clica no "próximo" nunca vê o slide 3. Se ele tinha a promoção principal, a maioria das visitas não viu. Por isso o carrossel serve bem para várias fotos do mesmo produto, em que perder um quadro não esconde a oferta, e serve mal para três mensagens diferentes.
Como montar o carrossel passo a passo
- Marque a galeria como uma
sectioncom nome, e cada slide como item de lista. - Use
buttonpara anterior, próximo e pausar, sempre com texto, mesmo que visualmente vire ícone. - Esconda os slides inativos com o atributo
hidden, que os tira da tela, do Tab e do leitor de tela de uma vez. - Mostre a posição em texto e anuncie a troca manual numa região viva educada.
- Se houver avanço automático, pause quando o mouse entra, quando o foco entra e quando o sistema pede menos movimento.
- Pare de vez o avanço automático assim que a pessoa usa os botões. Ela assumiu o controle.
<section data-carrossel aria-roledescription="carrossel" aria-label="Fotos da cadeira Lume">
<div>
<button type="button" data-acao="anterior">Foto anterior</button>
<p data-posicao aria-live="polite">Foto 1 de 4</p>
<button type="button" data-acao="proximo">Próxima foto</button>
<button type="button" data-acao="pausar">Pausar</button>
</div>
<ul>
<li data-slide><img src="/img/lume-frente.jpg" alt="Cadeira Lume de frente, estofado cinza e pés de madeira clara"></li>
<li data-slide hidden><img src="/img/lume-lado.jpg" alt="Cadeira Lume de perfil, mostrando a inclinação do encosto" loading="lazy"></li>
<li data-slide hidden><img src="/img/lume-costas.jpg" alt="Encosto da cadeira Lume visto por trás" loading="lazy"></li>
<li data-slide hidden><img src="/img/lume-sala.jpg" alt="Quatro cadeiras Lume em volta de uma mesa redonda" loading="lazy"></li>
</ul>
</section>
Os controles vêm antes dos slides no HTML, então quem usa teclado encontra os botões primeiro. O loading="lazy" evita baixar fotos que talvez ninguém veja, o que ajuda no tempo de carregamento discutido em site mais rápido: por onde começar.
O script, sem dependências
const raiz = document.querySelector('[data-carrossel]');
const slides = Array.from(raiz.querySelectorAll('[data-slide]'));
const posicao = raiz.querySelector('[data-posicao]');
const botaoPausa = raiz.querySelector('[data-acao="pausar"]');
const menosMovimento = window.matchMedia('(prefers-reduced-motion: reduce)');
const AUTOMATICO = true; // mude para false e o carrossel nunca anda sozinho
const INTERVALO = 7000;
let atual = 0;
let timer = null;
let pausadoPeloUsuario = !AUTOMATICO || menosMovimento.matches;
function mostrar(indice) {
slides[atual].hidden = true;
atual = (indice + slides.length) % slides.length;
slides[atual].hidden = false;
posicao.textContent = 'Foto ' + (atual + 1) + ' de ' + slides.length;
}
function iniciar() {
if (timer || pausadoPeloUsuario) return;
posicao.setAttribute('aria-live', 'off');
timer = setInterval(() => mostrar(atual + 1), INTERVALO);
}
function parar() {
clearInterval(timer);
timer = null;
posicao.setAttribute('aria-live', 'polite');
}
function pausarDeVez() {
pausadoPeloUsuario = true;
parar();
botaoPausa.textContent = 'Retomar';
}
raiz.querySelector('[data-acao="anterior"]').addEventListener('click', () => {
pausarDeVez();
mostrar(atual - 1);
});
raiz.querySelector('[data-acao="proximo"]').addEventListener('click', () => {
pausarDeVez();
mostrar(atual + 1);
});
botaoPausa.addEventListener('click', () => {
if (pausadoPeloUsuario) {
pausadoPeloUsuario = false;
botaoPausa.textContent = 'Pausar';
iniciar();
} else {
pausarDeVez();
}
});
raiz.addEventListener('mouseenter', parar);
raiz.addEventListener('mouseleave', iniciar);
raiz.addEventListener('focusin', parar);
raiz.addEventListener('focusout', (e) => {
if (!raiz.contains(e.relatedTarget)) iniciar();
});
menosMovimento.addEventListener('change', () => {
if (menosMovimento.matches) pausarDeVez();
});
if (pausadoPeloUsuario) botaoPausa.textContent = 'Retomar';
if (!AUTOMATICO) botaoPausa.hidden = true;
iniciar();
Por que a região viva muda de estado
Enquanto o carrossel gira sozinho, aria-live fica em off: anunciar "Foto 3 de 4" a cada sete segundos interromperia o leitor de tela no meio de outro texto. Quando está parado e a pessoa clica em próximo, vira polite, e a troca é anunciada no fim da fala atual. É a mesma lógica do padrão de carrossel do guia de práticas de ARIA do W3C.
O botão de pausa troca o texto entre "Pausar" e "Retomar" em vez de usar aria-pressed, para o nome dizer o que acontece no próximo clique. E o intervalo de sete segundos é uma escolha deliberada: com dois ou três segundos, quase ninguém termina de olhar a foto.
Como testar em dois minutos
Primeiro, só teclado: aperte Tab a partir do topo da página e confirme que o foco passa pelos três botões e pela foto visível, nunca por uma foto escondida. Segundo, ative "reduzir movimento" nas configurações de acessibilidade do sistema, recarregue e confirme que nada anda sozinho. Terceiro, ligue o leitor de tela do sistema (NVDA no Windows, VoiceOver no Mac ou no celular), clique em próxima foto e ouça se a posição nova é anunciada sem atropelar o que estava sendo lido.
Erros comuns
- Bolinhas como único controle. Pontos de 8 pixels sem nome não dizem nada ao leitor de tela e são difíceis de acertar no celular.
- Esconder slide só com
opacity: 0ou fora da tela. O link dentro do slide invisível continua no Tab, e o foco some num lugar que a pessoa não vê. - Avançar com o foco dentro do carrossel. A pessoa está num botão do slide 2 e ele desaparece embaixo dela.
- Ignorar
prefers-reduced-motion. Quem configurou o sistema para reduzir movimento, às vezes por enjoo e vertigem, recebe a animação mesmo assim. - Colocar três mensagens comerciais no mesmo carrossel. A segunda e a terceira são vistas por uma fração das visitas e ficam fora do alcance de quem só rola a página.
- Imagem sem
altdescritivo. O leitor anuncia "Foto 2 de 4" e depois o nome do arquivo.
Checklist
- Anterior, próximo e pausar são
buttoncom texto. - A posição atual aparece em texto, como "Foto 2 de 4".
- Slides inativos usam
hiddene não recebem foco. - O avanço automático, se existir, para com mouse, com foco e com menos movimento ativado.
- Usar anterior ou próximo desliga o avanço automático.
- Cada imagem tem
altque descreve o que a foto mostra. - Nenhuma informação essencial existe só num slide que não é o primeiro.
Componentes visuais precisam de nome e foco, mesmo os feitos só com CSS, como lembra componente CSS não dispensa nome. Agora abra a home do seu site, não toque em nada por quinze segundos e conte: quantas vezes o conteúdo mudou sozinho, e o que estava no último slide que a maioria das visitas nunca viu?
Referência: http://www.devmedia.com.br/criando-slideshow-do-zero-com-jquery-sem-usar-plugins/28297