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. Você aprenderá como controlar o fluxo de eventos e otimizar a manipulação de elementos dinâmicos.
Nesta aula, vamos explorar tópicos avançados sobre eventos em JavaScript, fundamentais para criar aplicações web interativas e eficientes. Entender como os eventos se propagam pelo DOM, como delegar eventos para elementos pai e como interromper comportamentos padrão ou a propagação é essencial para evitar bugs e melhorar a performance.
Vamos começar com a propagação de eventos, um conceito que descreve a ordem em que os elementos recebem um evento quando ele ocorre em um elemento aninhado. Em seguida, veremos a delegação de eventos, uma técnica poderosa para lidar com eventos em muitos elementos de forma eficiente. Por fim, aprenderemos a usar preventDefault para cancelar ações padrão do navegador e stopPropagation para interromper a propagação do evento.
Propagação (bubbling/capturing)
Quando um evento ocorre em um elemento, ele não é disparado apenas naquele elemento, mas sim em todos os seus ancestrais. Esse processo é chamado de propagação de eventos. Existem três fases: a fase de captura (capturing), a fase alvo (target) e a fase de bolha (bubbling). Na fase de captura, o evento desce do elemento raiz até o alvo. Na fase alvo, o evento atinge o elemento que disparou o evento. Na fase de bolha, o evento sobe do alvo de volta ao elemento raiz.
Por padrão, os ouvintes de eventos são acionados na fase de bolha. Para usar a fase de captura, você pode passar true como terceiro parâmetro para addEventListener. Entender a propagação é crucial para evitar comportamentos inesperados e para implementar padrões como delegação de eventos.
// Exemplo de bubbling: clique no botão dispara eventos no botão, no div e no 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'));
// Ao clicar no botão, a ordem será: Botão, Div, Body
// Exemplo de capturing: usando true
document.querySelector('div').addEventListener('click', () => console.log('Div capturing'), true);
document.body.addEventListener('click', () => console.log('Body capturing'), true);
// Ao clicar no botão, a ordem será: Body capturing, Div capturing, Botão
Delegação de eventos
A delegação de eventos é uma técnica que aproveita a propagação de eventos para gerenciar eventos de forma eficiente. Em vez de adicionar um ouvinte a cada elemento filho, você adiciona um único ouvinte a um elemento pai e usa a propriedade event.target para identificar qual filho foi clicado. Isso é especialmente útil quando você tem muitos elementos ou elementos que são adicionados dinamicamente.
Por exemplo, em uma lista com muitos itens, adicionar um ouvinte a cada li seria ineficiente. Com delegação, adicionamos um ouvinte ao ul e verificamos se o clique ocorreu em um li. Isso reduz o número de ouvintes e simplifica o código.
// Delegação de eventos: clique em qualquer li da lista
document.querySelector('ul').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('Clicou no item:', event.target.textContent);
}
});
// Adicionando um novo item dinamicamente
const novoItem = document.createElement('li');
novoItem.textContent = 'Item novo';
document.querySelector('ul').appendChild(novoItem);
// O clique no novo item também será capturado sem precisar adicionar um novo ouvinte
preventDefault e stopPropagation
preventDefault() é um método do objeto Event que impede a ação padrão do navegador associada ao evento. Por exemplo, clicar em um link (<a>) normalmente navega para uma URL, mas com preventDefault() podemos cancelar essa navegação. É comumente usado em formulários para evitar o envio padrão e validar os dados antes.
Já stopPropagation() interrompe a propagação do evento nas fases de captura ou bolha. Isso impede que os ouvintes de eventos em elementos pai sejam acionados. É útil quando você quer que apenas o elemento alvo lide com o evento, sem afetar os ancestrais.
// preventDefault: cancelar o envio de um formulário
document.querySelector('form').addEventListener('submit', function(event) {
event.preventDefault();
console.log('Formulário não será enviado');
});
// stopPropagation: impedir que o clique em um botão afete o div pai
document.querySelector('button').addEventListener('click', function(event) {
event.stopPropagation();
console.log('Clique no botão, mas não propaga');
});
document.querySelector('div').addEventListener('click', function() {
console.log('Div não será acionado');
});
Boas práticas
Ao trabalhar com eventos avançados, lembre-se de: (1) usar delegação sempre que possível para melhorar performance e simplificar o código; (2) evitar o uso excessivo de stopPropagation(), pois pode quebrar funcionalidades esperadas em outros componentes; (3) sempre considerar a propagação ao depurar eventos; e (4) usar preventDefault() apenas quando necessário, para não prejudicar a acessibilidade.
Referências
- MDN: stopPropagation
- MDN: preventDefault
- MDN: addEventListener
- MDN: eventPhase
- JavaScript.info: Bubbling and capturing
Exercícios
Crie um layout com três divs aninhadas (externa, média e interna). Adicione um ouvinte de clique a cada uma. Ao clicar na div interna, mostre no console a ordem dos eventos (bubbling).
✓ Resposta:<div id="externa"> <div id="media"> <div id="interna">Clique aqui</div> </div> </div> <script> document.getElementById('externa').addEventListener('click', () => console.log('Externa')); document.getElementById('media').addEventListener('click', () => console.log('Média')); document.getElementById('interna').addEventListener('click', () => console.log('Interna')); // Ao clicar na interna, a ordem será: Interna, Média, Externa (bubbling) </script>Usando delegação, crie uma lista não ordenada com 5 itens e um ouvinte no
ulque exibe o texto do item clicado.✓ Resposta:<ul id="lista"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> <li>Item 4</li> <li>Item 5</li> </ul> <script> document.getElementById('lista').addEventListener('click', function(event) { if (event.target.tagName === 'LI') { console.log(event.target.textContent); } }); </script>Faça um formulário com um campo de texto e um botão de envio. Use
preventDefaultpara evitar o envio e exiba uma mensagem de validação se o campo estiver vazio.✓ Resposta:<form id="meuForm"> <input type="text" id="nome" placeholder="Digite seu nome"> <button type="submit">Enviar</button> </form> <script> document.getElementById('meuForm').addEventListener('submit', function(event) { event.preventDefault(); const nome = document.getElementById('nome').value; if (nome.trim() === '') { alert('O campo nome é obrigatório.'); } else { alert('Formulário válido!'); } }); </script>Crie um botão dentro de uma div. Adicione ouvintes de clique no botão e na div. No ouvinte do botão, use
stopPropagationpara impedir que o clique no botão dispare o ouvinte da div.✓ Resposta:<div id="container"> <button id="meuBotao">Clique</button> </div> <script> document.getElementById('meuBotao').addEventListener('click', function(event) { event.stopPropagation(); console.log('Botão clicado'); }); document.getElementById('container').addEventListener('click', function() { console.log('Div clicada'); }); // Ao clicar no botão, apenas 'Botão clicado' aparece </script>Explique a diferença entre
event.targeteevent.currentTargetem um evento delegado. Dê um exemplo.✓ Resposta:// event.target é o elemento que disparou o evento (o alvo original). // event.currentTarget é o elemento que possui o ouvinte (o elemento pai na delegação). // Exemplo: <ul id="lista"> <li>Item 1</li> <li>Item 2</li> </ul> <script> document.getElementById('lista').addEventListener('click', function(event) { console.log('target:', event.target.tagName); // LI console.log('currentTarget:', event.currentTarget.tagName); // UL }); </script>