addEventListener: Fundamentos e Sintaxe
O método addEventListener é a forma moderna e recomendada de vincular eventos a elementos do DOM. Diferentemente da abordagem inline (onclick="..."), ele permite múltiplos listeners no mesmo elemento, melhor separação de responsabilidades e acesso a recursos avançados como opções de captura.
A sintaxe básica é elemento.addEventListener(evento, callback, opcoes). Aqui está um exemplo prático:
// Exemplo 1: Clique simples
const botao = document.getElementById('meuBotao');
botao.addEventListener('click', function(event) {
console.log('Botão clicado!');
console.log('Alvo:', event.target);
});
// Exemplo 2: Múltiplos listeners no mesmo elemento
botao.addEventListener('mouseenter', () => {
botao.style.backgroundColor = '#3498db';
});
botao.addEventListener('mouseleave', () => {
botao.style.backgroundColor = '#2c3e50';
});
// Exemplo 3: Remover listener (necessário guardar referência)
function handleClick(event) {
console.log('Removerei este listener');
}
botao.addEventListener('click', handleClick);
botao.removeEventListener('click', handleClick);
Uma vantagem crucial é o parâmetro options. Com { once: true }, o listener executa apenas uma vez. Com { passive: true }, melhora a performance em eventos como scroll:
window.addEventListener('scroll', () => {
console.log('Scrollando...');
}, { passive: true });
// Executa apenas uma vez
document.addEventListener('DOMContentLoaded', () => {
console.log('Documento carregado!');
}, { once: true });
Propagação: Entendendo Bubble e Capture
Eventos no DOM não ocorrem apenas no elemento alvo—eles percorrem uma árvore em fases: captura (do topo para o alvo) e bubbling (do alvo de volta ao topo). Compreender isso é essencial para evitar comportamentos inesperados.
<div id="avô" style="padding: 20px; background: #ecf0f1;">
<div id="pai" style="padding: 20px; background: #bdc3c7;">
<button id="filho">Clique aqui</button>
</div>
</div>
<script>
const avô = document.getElementById('avô');
const pai = document.getElementById('pai');
const filho = document.getElementById('filho');
// Fase de BUBBLING (padrão)
filho.addEventListener('click', () => console.log('1. Filho'));
pai.addEventListener('click', () => console.log('2. Pai'));
avô.addEventListener('click', () => console.log('3. Avô'));
// Resultado: 1. Filho → 2. Pai → 3. Avô
// Usando capture (true como terceiro parâmetro)
avô.addEventListener('click', () => console.log('A. Avô (captura)'), true);
pai.addEventListener('click', () => console.log('B. Pai (captura)'), true);
filho.addEventListener('click', () => console.log('C. Filho (captura)'), true);
// Resultado: A. Avô → B. Pai → C. Filho → [depois bubbling]
</script>
Para parar a propagação, use event.stopPropagation(). Para prevenir o comportamento padrão (como envio de formulário), use event.preventDefault():
const form = document.querySelector('form');
form.addEventListener('submit', (event) => {
event.preventDefault(); // Impede envio do formulário
console.log('Validação customizada...');
// Aqui você faria validações
if (validado) {
form.submit(); // Envia manualmente se válido
}
});
const link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.stopPropagation(); // Para bubbling, mas permite comportamento padrão
event.preventDefault(); // Previne navegação
console.log('Link interceptado');
});
Delegação de Eventos: Eficiência em Escala
Delegação é uma técnica poderosa: ao invés de adicionar listeners em cada elemento, você adiciona um no container pai e usa event.target para identificar qual elemento foi clicado. Isso economiza memória e funciona automaticamente com elementos criados dinamicamente.
<ul id="lista">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
<button id="adicionarItem">Adicionar item</button>
<script>
const lista = document.getElementById('lista');
const botao = document.getElementById('adicionarItem');
// SEM delegação (problema com elementos novos)
// document.querySelectorAll('.item').forEach(item => {
// item.addEventListener('click', handler); // Não funciona para novos itens!
// });
// COM delegação (abordagem correta)
lista.addEventListener('click', (event) => {
// Verifica se o clique foi em um .item
if (event.target.classList.contains('item')) {
console.log('Clicou em:', event.target.textContent);
event.target.style.backgroundColor = '#f1c40f';
}
});
// Adicionar items dinamicamente
botao.addEventListener('click', () => {
const novoItem = document.createElement('li');
novoItem.className = 'item';
novoItem.textContent = `Item ${lista.children.length + 1}`;
lista.appendChild(novoItem); // Listener já funciona aqui!
});
// Exemplo mais complexo: delegação com closest()
const container = document.getElementById('lista');
container.addEventListener('click', (event) => {
const item = event.target.closest('.item'); // Sobe a árvore procurando
if (item) {
console.log('Item encontrado:', item.textContent);
}
});
</script>
A delegação é especialmente útil em listas, tabelas e componentes dinâmicos. Use event.target.closest(selector) para encontrar o elemento mais próximo que corresponde ao seletor.
Conclusão
Dominar eventos no DOM significa: (1) usar addEventListener para flexibilidade e múltiplos handlers, (2) compreender a propagação (bubbling e captura) e controlar com stopPropagation() quando necessário, e (3) aplicar delegação de eventos para código mais limpo e eficiente, especialmente com conteúdo dinâmico. Esses três pilares são fundamentais para qualquer desenvolvedor JavaScript moderno.