Capítulo 19
O DOM e os eventos
5 min de leitura 833 palavras 1 gravura
Você já usou JavaScript no console e em páginas. Agora vamos entender como o programa encontra e altera os elementos da página. O DOM (Document Object Model, modelo de objeto do documento) é a árvore que o navegador monta a partir do HTML. Cada tag vira um nó. O script pega um nó, muda texto, classe ou atributo, e a tela acompanha.
O exemplo está em exemplos/19-dom/. É um interruptor: o botão acende e apaga o fundo. Enter e Espaço também alternam quando o botão está focado. O navegador já oferece esse comportamento ao button.
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
- Entender a página como árvore:
documentna raiz. - Encontrar nós com
querySelectorequerySelectorAll. - Trocar texto com
textContente classe comclassList. - Esperar ação com
addEventListeneremclickekeydown. - Ligar o interruptor da oficina no clique e no teclado.
A árvore da página
O arquivo HTML no disco é texto. O navegador lê esse texto e monta uma árvore. document é o objeto global que representa essa árvore. Filhos de body são cabeçalho, main, botões, parágrafos.
Quando o script roda depois do HTML, os nós já existem. Por isso o <script src="script.js"> fica no fim do body neste exemplo. Se o script rodar no head sem esperar, getElementById devolve null.
Erro comum: o script no head tenta pegar #interruptor antes do botão nascer. A página quebra com “Cannot read properties of null”. Mova o script para o fim do body ou use o evento DOMContentLoaded.

querySelector e querySelectorAll
querySelector recebe um seletor CSS e devolve o primeiro elemento que combina.
const botao = document.querySelector("#interruptor");
const status = document.querySelector("#status");
#interruptor é o id. .aceso seria uma classe. button seria o tipo.
querySelectorAll devolve todos os que combinam, numa lista parecida com array. Você percorre com for e length.
const alvos = document.querySelectorAll(".js-alvo");
for (let i = 0; i < alvos.length; i += 1) {
alvos[i].textContent = "Atualizado";
}
Se ninguém combinar, querySelector devolve null. querySelectorAll devolve uma lista vazia, não null. Sempre confira antes de usar o resultado.
textContent e classList
textContent lê ou grava o texto visível do nó. Não interpreta HTML. Para um status de lâmpada, é o que você quer.
classList é a lista de classes CSS daquele elemento. add, remove e toggle mexem nela sem apagar as outras classes.
document.body.classList.toggle("aceso", aceso);
status.textContent = aceso
? "A lâmpada está acesa."
: "A lâmpada está apagada.";
botao.textContent = aceso ? "Apagar" : "Acender";
O segundo argumento de toggle força o estado: true adiciona, false tira. O CSS do exemplo pinta o fundo quando body tem .aceso.
Não escreva className = "aceso" se o elemento já tem outras classes: você as apagaria todas.
addEventListener: click e keydown
Um evento é um aviso: a pessoa clicou, apertou uma tecla, a imagem carregou. Um ouvinte (addEventListener) diz qual função roda quando aquele aviso chega.
const botao = document.getElementById("interruptor");
let aceso = false;
function alternar() {
aceso = !aceso;
document.body.classList.toggle("aceso", aceso);
status.textContent = aceso
? "A lâmpada está acesa."
: "A lâmpada está apagada.";
botao.textContent = aceso ? "Apagar" : "Acender";
}
botao.addEventListener("click", alternar);
!aceso inverte o boolean: ligado vira desligado.
keydown dispara quando a tecla é pressionada. O objeto evento traz evento.key ("Enter", " " para espaço).
// O button já transforma Enter e Espaço em clique.
Com o foco no botão, Enter e Espaço já disparam o clique. Basta escutar o evento click; um segundo atalho poderia alternar o estado duas vezes.
Pare e experimente. Clique no botão. Depois use Tab para deixar o foco no botão e aperte Enter. O fundo deve alternar nas duas ações. Recarregue se o script não atualizar.
O interruptor completo
No HTML do exemplo:
<p id="status">A lâmpada está apagada.</p>
<p>
<button type="button" id="interruptor">Acender</button>
</p>
type="button" impede o botão de se comportar como envio de formulário. O rótulo muda no JavaScript, então quem usa leitor de tela ouve o estado atual.
O teclado não é extra: é o mesmo interruptor. Se só o mouse funciona, a página ainda não está pronta.
Erro comum: escutar keyup esperando o espaço e ver a página rolar. Prefira o botão nativo: focado, ele oferece Enter e Espaço sem um atalho global que atrapalhe links.
Atividades
- Qual a diferença entre
querySelectorequerySelectorAll? - Por que o script deste exemplo fica no fim do
body? - O que
classList.toggle("aceso", false)faz? - No exemplo, mude a mensagem acesa para “O terreiro está iluminado.” Recarregue e clique. O que você espera ver?
Respostas comentadas
- O primeiro devolve um nó ou
null. O segundo devolve uma lista com zero ou mais nós. Para um interruptor, um id basta. Para vários cartões, useAll. - O HTML do botão e do parágrafo precisa existir antes do script perguntar por eles. No fim do
body, a árvore já está montada. - Tira a classe
acesose ela estiver lá. O segundo argumentofalsesignifica “não quero essa classe”. - O parágrafo
#statusmostra a frase nova quandoacesoé verdadeiro. O fundo continua dependendo da classe nobody.
Referências
O arquivo completo
Referência do exemplo 19-dom, reproduzida aqui para que você possa conferir sem sair da página.
index.html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>DOM</title>
<link href="lab.css" rel="stylesheet"/>
<style>
.aceso { background: #c45c26; color: #fff; padding: 1rem; }
</style>
<style>body{overflow-wrap:anywhere}img,input,textarea,select,iframe{max-width:100%}img{height:auto}fieldset,.cartao{min-width:0}pre{max-width:100%;overflow:auto}:focus-visible{outline:3px solid #005fcc;outline-offset:3px}.atalho-conteudo{position:absolute;left:.5rem;top:-6rem;background:white;color:#172f50;padding:.5rem;z-index:999}.atalho-conteudo:focus{top:.5rem}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}</style></head>
<body><a class="atalho-conteudo" href="#conteudo">Pular para o conteúdo</a><main id="conteudo" tabindex="-1">
<h1>O DOM e os eventos</h1>
<p id="status">A lâmpada está apagada.</p>
<p>
<button id="interruptor" type="button">Acender</button>
</p>
</main><script src="script.js"></script></body>
</html>
script.js
const status = document.getElementById("status");
const botao = document.getElementById("interruptor");
let aceso = false;
function alternar() {
aceso = !aceso;
document.body.classList.toggle("aceso", aceso);
status.textContent = aceso ? "A lâmpada está acesa." : "A lâmpada está apagada.";
botao.textContent = aceso ? "Apagar" : "Acender";
}
botao.addEventListener("click", alternar);