Eventos avançado
Esta aula aborda conceitos avançados de eventos em JavaScript, incluindo propagação (bubbling e capturing), delegação de eventos e os métodos preventDefault e stopPropagation. Explica como o fluxo de eventos funciona no DOM e como controlá-lo para criar interações mais eficientes e previsíveis.
Eventos são a base da interatividade em páginas web, mas seu comportamento nem sempre é intuitivo. Quando um evento é disparado em um elemento, ele não ocorre apenas naquele elemento: ele percorre toda a árvore DOM, passando por ancestrais e descendentes. Esse percurso é chamado de propagação de eventos, e entender seus mecanismos é essencial para evitar comportamentos inesperados e para escrever código mais limpo e eficiente.
Nesta aula, vamos explorar os três tópicos fundamentais para dominar eventos avançados: propagação (fases de bubbling e capturing), delegação de eventos (uma técnica poderosa para lidar com múltiplos elementos) e os métodos preventDefault e stopPropagation, que nos dão controle fino sobre o fluxo de eventos.
Propagação (bubbling/capturing)
A propagação de eventos no DOM ocorre em três fases: captura, alvo e bolha. Quando um evento é disparado, ele primeiro desce do elemento raiz até o elemento alvo (fase de captura), atinge o elemento alvo (fase de alvo) e então sobe novamente até o elemento raiz (fase de bolha). Por padrão, os event listeners são executados na fase de bolha, mas é possível registrá-los na fase de captura passando true como terceiro argumento de addEventListener.
Entender essas fases é crucial para prever a ordem de execução dos listeners e para depurar problemas onde eventos parecem disparar múltiplas vezes ou em elementos inesperados. O bubbling, por exemplo, é o que permite que um clique em um botão dentro de um div também dispare o listener do div, a menos que seja interrompido.
// Exemplo de bubbling: ao clicar no botão, o evento sobe até o body
document.querySelector('button').addEventListener('click', () => console.log('Botão'));
document.querySelector('div').addEventListener('click', () => console.log('Div'));
document.body.addEventListener('click', () => console.log('Body'));
// Clique no botão: Botão, Div, Body
// Capturing: listener na fase de captura
document.querySelector('div').addEventListener('click', () => console.log('Div captura'), true);
// Ordem: Div captura (antes do alvo), depois Botão, depois Div (bolha)
Delegação de eventos
Delegação de eventos é uma técnica que aproveita o bubbling para gerenciar eventos de múltiplos elementos de forma eficiente. Em vez de adicionar um listener a cada elemento filho, adiciona-se um único listener a um ancestral comum e, dentro dele, verifica-se qual elemento filho originou o evento (usando event.target). Isso é especialmente útil para elementos que são adicionados dinamicamente ou quando há muitos elementos similares.
A delegação reduz o número de listeners na página, melhora a performance e simplifica o código. Por exemplo, em uma lista de itens, em vez de adicionar um listener para cada li, adicionamos um listener ao ul e verificamos se o clique foi em um li.
// Delegação: listener no pai
document.querySelector('ul').addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Item clicado:', event.target.textContent);
}
});
// Itens adicionados dinamicamente também funcionam
const ul = document.querySelector('ul');
ul.innerHTML += '<li>Novo item</li>';
preventDefault e stopPropagation
preventDefault() é um método do objeto Event que impede o comportamento padrão do navegador associado ao evento. Por exemplo, clicar em um link (<a>) normalmente navega para uma URL; com preventDefault(), podemos cancelar essa navegação e executar nossa própria lógica. É comumente usado em formulários para evitar o envio padrão e validar dados via JavaScript.
stopPropagation() interrompe a propagação do evento nas fases de captura e/ou bolha. Isso impede que listeners em elementos ancestrais (ou descendentes, se na fase de captura) recebam o evento. É útil quando queremos que um evento seja tratado apenas em um elemento específico, sem afetar outros. No entanto, deve ser usado com cuidado, pois pode quebrar funcionalidades que dependem da propagação, como delegação de eventos.
// preventDefault: cancelar navegação de link
document.querySelector('a').addEventListener('click', (event) => {
event.preventDefault();
console.log('Link clicado, mas não navegou');
});
// stopPropagation: impedir que o evento atinja o pai
document.querySelector('button').addEventListener('click', (event) => {
event.stopPropagation();
console.log('Apenas botão');
});
document.querySelector('div').addEventListener('click', () => {
console.log('Div não será chamada');
});
Boas práticas
Ao trabalhar com eventos avançados, lembre-se de:
- Usar delegação sempre que possível para melhorar performance e lidar com elementos dinâmicos.
- Evitar
stopPropagation()a menos que seja absolutamente necessário, pois pode causar efeitos colaterais inesperados. - Verificar
event.targetcom cuidado, especialmente em delegação, para garantir que o elemento esperado foi clicado. - Considerar o uso de
event.stopImmediatePropagation()se precisar interromper também outros listeners no mesmo elemento.
Referências
- MDN: addEventListener
- MDN: stopPropagation
- MDN: preventDefault
- MDN: Event bubbling
- MDN: eventPhase
- Eloquent JavaScript: Events
Exercícios
-
Crie uma página HTML com uma lista não ordenada (
<ul>) contendo três itens (<li>). Adicione um event listener ao<ul>que, ao clicar em qualquer<li>, exiba no console o texto do item clicado. Use delegação de eventos.✓ Resposta:// HTML: <ul id="lista"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul> document.getElementById('lista').addEventListener('click', function(event) { if (event.target.tagName === 'LI') { console.log(event.target.textContent); } }); -
Adicione um link (
<a href="https://example.com">) em uma página. UsepreventDefault()para impedir a navegação e, em vez disso, exiba um alerta com "Link clicado".✓ Resposta:// HTML: <a href="https://example.com" id="meuLink">Clique aqui</a> document.getElementById('meuLink').addEventListener('click', function(event) { event.preventDefault(); alert('Link clicado'); }); -
Considere a seguinte estrutura HTML:
<div id="pai"><button id="filho">Clique</button></div>. Adicione listeners de clique tanto nodivquanto nobutton. UsestopPropagation()no listener do botão para que o listener dodivnão seja disparado. Teste clicando no botão.✓ Resposta:document.getElementById('pai').addEventListener('click', function() { console.log('Div clicado'); }); document.getElementById('filho').addEventListener('click', function(event) { event.stopPropagation(); console.log('Botão clicado'); }); // Ao clicar no botão, apenas 'Botão clicado' aparece. -
Explique a diferença entre as fases de captura e bolha na propagação de eventos. Dê um exemplo de código que demonstre a ordem de execução quando um listener é adicionado na fase de captura.
✓ Resposta:A fase de captura ocorre do elemento raiz até o alvo; a fase de bolha, do alvo até a raiz. Listeners padrão operam na bolha. Para captura, usa-se
addEventListener(type, listener, true). Exemplo:// HTML: <div id="pai"><button id="filho">Clique</button></div> document.getElementById('pai').addEventListener('click', function() { console.log('Pai (bolha)'); }); document.getElementById('pai').addEventListener('click', function() { console.log('Pai (captura)'); }, true); document.getElementById('filho').addEventListener('click', function() { console.log('Filho (alvo)'); }); // Ordem ao clicar no botão: Pai (captura), Filho (alvo), Pai (bolha) -
Crie um formulário com um campo de texto e um botão de envio. Adicione um listener de submit ao formulário que use
preventDefault()para evitar o envio e exiba o valor do campo em um alerta.✓ Resposta:// HTML: <form id="meuForm"><input type="text" id="nome" /><button type="submit">Enviar</button></form> document.getElementById('meuForm').addEventListener('submit', function(event) { event.preventDefault(); const valor = document.getElementById('nome').value; alert('Valor: ' + valor); });