Programação Por Equipe DevBras

Capa do artigo: Slideshow acessível em JavaScript puro: a pessoa controla o tempo

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

  1. Marque a galeria como uma section com nome, e cada slide como item de lista.
  2. Use button para anterior, próximo e pausar, sempre com texto, mesmo que visualmente vire ícone.
  3. Esconda os slides inativos com o atributo hidden, que os tira da tela, do Tab e do leitor de tela de uma vez.
  4. Mostre a posição em texto e anuncie a troca manual numa região viva educada.
  5. Se houver avanço automático, pause quando o mouse entra, quando o foco entra e quando o sistema pede menos movimento.
  6. 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: 0 ou 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 alt descritivo. O leitor anuncia "Foto 2 de 4" e depois o nome do arquivo.

Checklist

  • Anterior, próximo e pausar são button com texto.
  • A posição atual aparece em texto, como "Foto 2 de 4".
  • Slides inativos usam hidden e 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 alt que 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?

Avalie este artigo

Referência: http://www.devmedia.com.br/criando-slideshow-do-zero-com-jquery-sem-usar-plugins/28297