O DOM: achar e mudar elementos
O que é o DOM
Quando o navegador lê o HTML, ele monta na memória uma árvore de objetos: o DOM. Cada tag vira um objeto que o JavaScript pode ler e alterar.
Alterar o DOM altera o que está na tela na hora, sem recarregar a página.
Achar elementos
// Um elemento, pelo seletor CSS
const titulo = document.querySelector("#titulo");
const primeiroCartao = document.querySelector(".cartao");
const botao = document.querySelector('button[type="submit"]');
// Todos os que casam
const cartoes = document.querySelectorAll(".cartao");
// A forma antiga, específica de id
const titulo2 = document.getElementById("titulo");
querySelector aceita qualquer seletor CSS: o mesmo que você aprendeu no
curso anterior. É o suficiente para quase tudo.
const elemento = document.querySelector("#nao-existe");
console.log(elemento); // → null
elemento.textContent = "x"; // TypeError: Cannot read properties of null
Percorrer uma lista de elementos
const cartoes = document.querySelectorAll(".cartao");
for (const cartao of cartoes) {
cartao.classList.add("visivel");
}
console.log(cartoes.length);
querySelectorAll devolve uma NodeList, que não é um array: ela tem
forEach e dá para percorrer com for...of, mas não tem map nem filter.
Para usá-los:
const textos = [...cartoes].map((c) => c.textContent);
Ler e escrever texto
const titulo = document.querySelector("#titulo");
console.log(titulo.textContent);
titulo.textContent = "Feira aberta hoje";
const campo = document.querySelector("#quantidade");
console.log(campo.value); // "3" - sempre texto
console.log(Number(campo.value)); // 3
campo.value = "5";
Elemento comum usa textContent. Campo de formulário usa value.
textContent e innerHTML
const alvo = document.querySelector("#saida");
alvo.textContent = "<strong>Olá</strong>";
// → mostra literalmente: <strong>Olá</strong>
alvo.innerHTML = "<strong>Olá</strong>";
// → mostra Olá em negrito
innerHTML interpreta o texto como HTML. Isso é útil; e perigoso:
// PERIGOSO: o comentário veio de outra pessoa
const comentario = pegarComentarioDoServidor();
alvo.innerHTML = comentario;
Se o comentário contiver <img src=x onerror="...">, o código embutido roda no
navegador de quem abrir a página, com o acesso que a pessoa tem. É o ataque
chamado XSS.
Atributos, classes e estilo
const imagem = document.querySelector("#foto");
imagem.setAttribute("src", "feira.jpg");
imagem.setAttribute("alt", "Bancas de frutas na orla");
console.log(imagem.getAttribute("alt"));
const cartao = document.querySelector(".cartao");
cartao.classList.add("destaque");
cartao.classList.remove("oculto");
cartao.classList.toggle("aberto"); // liga/desliga
console.log(cartao.classList.contains("destaque")); // → true
Prefira mexer em classes a mexer em style direto:
// Evite: o estilo fica espalhado entre o CSS e o JavaScript
cartao.style.backgroundColor = "yellow";
cartao.style.padding = "16px";
// Prefira: o estilo continua no CSS, e o JS só liga o estado
cartao.classList.add("destaque");
Criar elementos
const item = document.createElement("li");
item.textContent = "Farinha d'água - R$ 12,50/kg";
item.classList.add("produto");
const lista = document.querySelector("#produtos");
lista.append(item);
| Método | O que faz |
|---|---|
append | no fim, dentro do elemento |
prepend | no começo, dentro |
before | antes do elemento |
after | depois |
remove | tira o próprio elemento |
replaceWith | troca por outro |
Muitos elementos de uma vez
const PRODUTOS = [
{ nome: "Farinha d'água", precoEmCentavos: 1250 },
{ nome: "Açaí", precoEmCentavos: 2500 },
{ nome: "Tucumã", precoEmCentavos: 800 },
];
const lista = document.querySelector("#produtos");
const fragmento = document.createDocumentFragment();
for (const produto of PRODUTOS) {
const item = document.createElement("li");
item.textContent = `${produto.nome} - R$ ${(produto.precoEmCentavos / 100).toFixed(2)}/kg`;
fragmento.append(item);
}
lista.replaceChildren(fragmento);
Duas coisas importam aqui:
O fragmento. Inserir cem itens um a um faz o navegador recalcular o layout cem vezes. Montar tudo num fragmento e inserir uma vez é um cálculo só: e num aparelho simples a diferença se nota.
replaceChildren. Limpa o que havia e põe o novo, numa operação. Substitui o
antigo lista.innerHTML = "" seguido do preenchimento.
Navegar pela árvore
const item = document.querySelector(".produto");
item.parentElement; // o pai
item.children; // os filhos
item.nextElementSibling; // o irmão seguinte
item.closest(".cartao"); // o ancestral mais próximo que casa
closest é o mais útil dos quatro: a partir de um botão clicado, ele acha o
cartão inteiro a que aquele botão pertence.
Uma listagem completa
<main>
<h1>Feira do produtor</h1>
<p id="resumo"></p>
<ul id="produtos"></ul>
</main>
const PRODUTOS = [
{ nome: "Farinha d'água", precoEmCentavos: 1250, estoqueEmKg: 40 },
{ nome: "Açaí", precoEmCentavos: 2500, estoqueEmKg: 15 },
{ nome: "Tucumã", precoEmCentavos: 800, estoqueEmKg: 0 },
];
const formatar = (centavos) =>
(centavos / 100).toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
function montarItem(produto) {
const item = document.createElement("li");
item.classList.add("produto");
const nome = document.createElement("strong");
nome.textContent = produto.nome;
const preco = document.createElement("span");
preco.textContent = ` - ${formatar(produto.precoEmCentavos)}/kg`;
item.append(nome, preco);
if (produto.estoqueEmKg === 0) {
item.classList.add("esgotado");
const aviso = document.createElement("em");
aviso.textContent = " (esgotado)";
item.append(aviso);
}
return item;
}
function renderizar(produtos) {
const lista = document.querySelector("#produtos");
const resumo = document.querySelector("#resumo");
if (!lista || !resumo) return;
const disponiveis = produtos.filter((p) => p.estoqueEmKg > 0);
resumo.textContent = `${disponiveis.length} de ${produtos.length} produtos disponíveis`;
const fragmento = document.createDocumentFragment();
for (const produto of produtos) {
fragmento.append(montarItem(produto));
}
lista.replaceChildren(fragmento);
}
renderizar(PRODUTOS);
Repare: tudo com textContent, nada com innerHTML. A guarda if (!lista) no
começo evita o erro de null.
Exercícios
- Troque o texto de um
<h1>pelo JavaScript. - Selecione todos os elementos de uma classe e acrescente outra a cada um.
- Leia o valor de um campo e mostre-o num parágrafo.
- Escreva
<strong>oi</strong>comtextContente cominnerHTML. Compare. - Crie uma lista de cinco itens a partir de um array, usando fragmento.
- Use
closestpara achar o cartão a partir de um elemento interno. - Selecione um id que não existe e leia o erro ao usar o resultado.
Referências
- Manipulando documentos - MDN - a introdução ao DOM
querySelector- MDNtextContentvsinnerHTML- MDN - a seção de segurança
Testa o que você leu
5 perguntas sobre esta aula. Errar aqui não custa nada - a explicação vem junto da correção.