Pular para o conteúdo
murad.guide

A Web Aberta

Capítulo 51

DOM, eventos e acessibilidade

6 min de leitura 1.064 palavras 2 gravuras

Este capítulo liga o JavaScript vanilla ao HTML: selecionar, ouvir clique e teclado, e não estragar a acessibilidade. A Oficina Costeira é um coletivo fictício de design em João Pessoa. Este volume tem 52 capítulos. Esta é a 51.

O exemplo está em exemplos/51-dom-eventos-acessibilidade/. Use querySelector e addEventListener. jQuery aparece só como nota de “como era”. Ciclo: ler, copiar, mudar uma coisa, prever, recarregar, conferir. Teste com Tab, não só com o mouse.

O que você vai aprender

O DOM é o documento em forma de árvore

O navegador lê o HTML e monta uma árvore de nós: elementos, texto, atributos. Essa árvore é o DOM (modelo de objeto do documento). JavaScript não “pinta a tela” direto. Ele pede nós ao DOM e muda esses nós. A tela acompanha.

document é a raiz. document.querySelector("h1") devolve o primeiro h1 ou null.

const titulo = document.querySelector("h1");
if (titulo) {
  console.log(titulo.textContent);
}

Sempre pergunte se o nó existe. Página da agenda não tem data-grafico. O mesmo arquivo JS pode rodar em várias páginas se você proteger com if.

Mude só o seletor para h2. Preveja o primeiro h2 da página, ou null na home se não houver. Recarregue. Confira o console. Volte para o seletor que o exercício usa.

Erro comum: chamar titulo.textContent sem o if. Se titulo é null, o console mostra erro e o restante do script para.

Selecionar como no CSS

querySelector aceita seletor CSS. Um ponto, um #id, um [atributo]. querySelectorAll devolve uma lista. Não é um array completo, mas você percorre com for...of.

const raiz = document.querySelector("[data-grafico]");
const itens = document.querySelectorAll(".card");

for (const card of itens) {
  console.log(card.querySelector("h2")?.textContent);
}

O ?. evita erro se o h2 faltar. Mesmo assim, o HTML da Oficina Costeira deve ter o título. Script não desculpa card sem h2.

Não use getElementById e querySelector misturados sem motivo. Um estilo só. Neste volume: querySelector.

Mude só [data-grafico] para div. Preveja o primeiro div da página, que pode ser outro. Recarregue. Confira. Selecionar demais é tão ruim quanto não selecionar. Prefira um atributo de propósito (data-grafico).

Erro comum: seletor de classe sem ponto (card em vez de .card). Isso procura a tag <card>, que não existe.

Eventos: o clique não é o único

Evento é algo que acontece: clique, tecla, envio de formulário, fim do carregamento. addEventListener liga uma função a um evento num nó.

<p>
  <button type="button" data-aviso>Lembrar: nada é enviado</button>
</p>
<p id="recado" hidden></p>
const botao = document.querySelector("[data-aviso]");
const recado = document.querySelector("#recado");

if (botao && recado) {
  botao.addEventListener("click", function () {
    recado.hidden = false;
    recado.textContent = "Este formulário da Oficina Costeira não envia dados.";
  });
}

O botão é type="button", não submit. Não há rede. O recado vive na página, com texto, não num alert que trava o leitor.

Formulário demonstrativo:

const form = document.querySelector("form");
if (form) {
  form.addEventListener("submit", function (evento) {
    evento.preventDefault();
  });
}

preventDefault cancela o envio. A validação required do HTML ainda pode aparecer antes, no navegador. Você não grava nome nem e-mail.

Mude só o texto do recado. Preveja a frase nova depois do clique. Recarregue. Clique. Confira. Depois tente a mesma ação só com o teclado: Tab até o botão, Enter. Confira de novo.

Erro comum: onclick="..." no HTML. A mistura impede vários ouvintes e suja o documento. Ouça no JS.

Resumo dos conceitos: querySelector, addEventListener, aria-*

ARIA no que o JavaScript cria

ARIA descreve papéis e estados para a tecnologia assistiva. No HTML estático da Oficina Costeira você já usa aria-current, aria-label no nav, skip-link. No JS, se criar um controle, atualize o estado.

Exemplo: um botão que abre um painel fictício de números.

botao.setAttribute("aria-expanded", "true");
painel.hidden = false;

Quando fechar:

botao.setAttribute("aria-expanded", "false");
painel.hidden = true;

Não coloque ARIA de abas num HTML que, sem JS, é só uma lista. Sem script, a lista deve permanecer lista. Com script, se você virar abas, aí sim role, aria-selected e teclas. Este volume não exige o widget de abas. Exige: não mentir o estado, não tirar o foco, não capturar o Tab para sempre.

Foco visível continua no CSS (outline de 3 px). O script não deve fazer outline: none.

Mude só aria-expanded para "true" e esqueça de mostrar o painel. Preveja: o leitor pode anunciar aberto e a tela não mostra. Recarregue. Confira os dois lados. Estado e visual andam juntos.

Erro comum: div clicável com click e sem tabindex, sem papel de botão, sem Enter. Use button.

Infográfico acessível o bastante

O infográfico da Oficina Costeira não pode ser a única leitura. A lista <ul> vem primeiro. O div data-grafico recebe parágrafos com textContent. Se o script falhar, a lista resta.

const raiz = document.querySelector("[data-grafico]");
if (!raiz) {
  // A página ainda funciona: a lista HTML abaixo já conta a história.
} else {
  const barras = [
    { nome: "Identidade", valor: 8 },
    { nome: "Publicação", valor: 12 },
    { nome: "Oficina", valor: 5 }
  ];
  raiz.replaceChildren();
  for (const item of barras) {
    const p = document.createElement("p");
    p.textContent = item.nome + ": " + item.valor + " trabalhos fictícios";
    raiz.append(p);
  }
}

Se um dia as barras forem só cor, coloque o número no texto mesmo assim. Cor não é dado para quem não vê a cor.

Mude só os três valores no script e esqueça a lista HTML. Preveja divergência. Recarregue. Confira. Alinhe os dois. Acessibilidade aqui também é consistência.

Erro comum: innerHTML com string montada de dado desconhecido. Neste exercício os números são seus e fictícios, mas o hábito de textContent já evita a porta.

Como era o jQuery (nota curta)

Antigamente muitos tutoriais escreviam $("#recado").text("...") e $(botao).on("click", ...). Isso era jQuery: uma biblioteca que suavizava diferenças entre navegadores e encurtava a seleção.

Hoje querySelector e addEventListener fazem o trabalho deste volume. Você não vai baixar jQuery, não vai ligar CDN, não vai reescrever a Oficina Costeira em $. Se achar um snippet antigo, traduza: $(".card") → document.querySelectorAll(".card").

Não misture os dois. Não é exercício traduzir um plugin de carrossel.

python -m http.server 8769 --bind 127.0.0.1 --directory curso

Abra http://127.0.0.1:8769/exemplos/51-dom-eventos-acessibilidade/. Percorra com Tab. Encerre com Ctrl+C. Não use 0.0.0.0. Não publique. Formulário sem dado real.

Resumo dos conceitos: aria-*, addEventListener, querySelector

Atividades

  1. O que querySelector devolve quando o seletor não acha nada?
  2. Por que o botão de aviso é type="button" e não um div?
  3. O que preventDefault no submit está evitando neste volume?
  4. Se você cria um painel, quais dois lados precisam combinar?
  5. jQuery é obrigatório para ouvir um clique hoje?

Respostas comentadas

  1. null. Por isso o if antes de usar o nó.
  2. button já recebe foco, Enter e espaço. div não é controle. type="button" não envia formulário.
  3. O envio do formulário para uma URL. O exercício não grava e-mail nem mensagem.
  4. O visual (hidden ou equivalente) e o estado ARIA (aria-expanded ou similar). Um sem o outro mente.
  5. Não. addEventListener("click", ...) é vanilla e suficiente.

Referências

O arquivo completo

Referência do exemplo 51-dom-eventos-acessibilidade, reproduzida aqui para que você possa conferir sem sair da página.

assets/javascript/simulacao.js

const form=document.querySelector("#simulacao");if(form){form.querySelector("button[type=submit]").disabled=false;form.addEventListener("submit",event=>{event.preventDefault();document.querySelector("#retorno").textContent="Simulação concluída. Nada foi enviado ou guardado.";});}