Compreendendo o Borbulhamento de Eventos em JavaScript
Quando comecei minha jornada no desenvolvimento web, o borbulhamento de eventos foi um conceito que me intrigou profundamente. Inicialmente, parecia contraintuitivo, mas com uma análise mais detalhada, percebi que fazia todo o sentido. Como desenvolvedor web, você certamente se deparará com o borbulhamento de eventos em algum momento. Então, o que exatamente é esse fenômeno?
Para permitir a interação dos usuários com as páginas da web, o JavaScript utiliza eventos. Um evento é uma ocorrência ou ação que pode ser detectada e respondida pelo código que você escreve. Alguns exemplos são cliques do mouse, pressionamentos de teclas e o envio de formulários.
O JavaScript emprega ouvintes de eventos para identificar e reagir a esses eventos. Um ouvinte de evento é essencialmente uma função que aguarda a ocorrência de um evento específico na página. Por exemplo, um clique em um botão. Quando o ouvinte detecta o evento que está monitorando, ele executa o código correspondente. Todo esse processo de detectar e responder a eventos é conhecido como manipulação de eventos.
Imagine agora que temos três elementos em uma página: uma `div`, um `span` e um `button`. O botão está aninhado dentro do `span`, e o `span` está aninhado dentro da `div`. Veja uma representação visual disso abaixo:
Suponha que cada um desses elementos tenha um ouvinte de evento que monitora cliques e os registra no console. O que acontecerá se você clicar no botão?
Para testar por si mesmo, crie uma pasta com três arquivos: `index.html`, `style.css` e `app.js`.
No arquivo `index.html`, insira o seguinte código:
<!DOCTYPE html> <html lang="en"> <head> <title>Borbulhamento de Eventos</title> <link rel="stylesheet" href="https://wilku.top/the-hidden-key-to-dynamic-web-interactions/style.css"> </head> <body> <div> <span><button>Clique Aqui!</button></span> </div> <script src="app.js"></script> </body> </html>
No arquivo `style.css`, adicione o código abaixo para estilizar a `div` e o `span`:
div { border: 2px solid black; background-color: orange; padding: 30px; width: 400px; } span { display: inline-block; background-color: cyan; height: 100px; width: 200px; margin: 10px; padding: 20px; border: 2px solid black; }
E no arquivo `app.js`, insira o seguinte código, que adiciona ouvintes de eventos aos elementos `div`, `span` e `button`, todos monitorando cliques:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Você clicou em um elemento div") }) const span = document.querySelector('span'); span.addEventListener('click', () => { console.log("Você clicou em um elemento span") }) const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Você clicou em um botão") })
Agora, abra o arquivo HTML no seu navegador. Inspecione a página e clique no botão. O que você observa? A saída ao clicar no botão será algo como isto:
Ao clicar no botão, o ouvinte de evento do botão é acionado, e a mensagem é exibida no console. No entanto, os ouvintes de eventos nos elementos `span` e `div` também são ativados. Por que isso acontece?
Clicar no botão ativa o ouvinte de evento associado ao botão. No entanto, como o botão está dentro do `span`, clicar no botão significa que também estamos clicando no `span`, ativando seu ouvinte de evento.
Da mesma forma, o `span` está dentro da `div`, então clicar no `span` também significa que clicamos na `div`, e seu ouvinte de evento é ativado. Esse é o borbulhamento de eventos em ação.
O que é Borbulhamento de Eventos?
O borbulhamento de eventos é o mecanismo pelo qual um evento disparado em um conjunto aninhado de elementos HTML se propaga ou “borbulha” do elemento mais interno, onde foi originado, para cima na árvore DOM, atingindo o elemento raiz e acionando todos os ouvintes de eventos que estão monitorando esse evento específico.
Os ouvintes de eventos são ativados em uma ordem específica que acompanha o caminho que o evento percorre na árvore DOM. Veja a representação abaixo, que ilustra a estrutura HTML usada neste artigo:
Uma representação visual da árvore DOM para o borbulhamento de um evento de clique.
Esta árvore DOM mostra um botão aninhado dentro de um `span`, que por sua vez está dentro de uma `div`, que está aninhada no `body`, e este último está dentro do elemento `html`. Ao clicar no botão, o evento de clique inicialmente ativa o ouvinte de evento anexado ao próprio botão.
Como os elementos estão aninhados, o evento “sobe” na árvore DOM (borbulha para cima) para o elemento `span`, depois para a `div`, o `body` e, por fim, para o elemento `html`, ativando todos os ouvintes de eventos que estão monitorando um clique nessa sequência.
É por isso que os ouvintes de evento associados aos elementos `span` e `div` também são executados. Se tivéssemos ouvintes de eventos esperando por cliques no `body` ou no elemento `html`, eles também seriam acionados.
O nó DOM onde um evento realmente ocorre é conhecido como o alvo. No nosso caso, como o clique é no botão, o elemento `button` é o alvo do evento.
Como Impedir o Borbulhamento de Eventos
Para interromper a propagação de um evento na árvore DOM, podemos usar um método chamado `stopPropagation()` disponível no objeto de evento. Veja o exemplo abaixo, que usamos para adicionar um ouvinte de evento ao botão:
const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Você clicou em um botão"); })
Este código fará com que um evento borbulhe na árvore DOM quando o usuário clicar no botão. Para evitar isso, chamamos o método `stopPropagation()`, conforme mostrado abaixo:
const button = document.querySelector('button'); button.addEventListener('click', (e) => { console.log("Você clicou em um botão"); e.stopPropagation(); })
Um manipulador de eventos é a função que é executada quando um botão é clicado. Um ouvinte de eventos automaticamente passa um objeto de evento para este manipulador. No nosso caso, esse objeto é representado pela variável `e`, que é passada como parâmetro para o manipulador de eventos.
O objeto de evento, `e`, contém informações sobre o evento e também nos dá acesso a várias propriedades e métodos relacionados a ele. Um desses métodos é o `stopPropagation()`, que é utilizado para impedir o borbulhamento. Ao chamar `stopPropagation()` no ouvinte de evento do botão, evitamos que o evento se propague pela árvore DOM a partir do botão.
O resultado de clicar no botão depois de adicionar o método `stopPropagation()` será:
Utilizamos `stopPropagation()` para impedir que um evento suba para o elemento onde é usado. Por exemplo, se quiséssemos que o clique “subisse” do botão para o `span`, mas não fosse mais adiante, usaríamos `stopPropagation()` no ouvinte do `span`.
Captura de Eventos
A captura de eventos é o oposto do borbulhamento. Nesse processo, um evento desce do elemento mais externo até o elemento alvo, conforme ilustrado abaixo:
O caminho do evento de clique no processo de captura de eventos.
Por exemplo, em nosso caso, ao clicar no botão, no processo de captura de eventos, os ouvintes da `div` são os primeiros a serem ativados. Em seguida, os ouvintes do `span`, e finalmente os do elemento alvo.
No entanto, o borbulhamento de eventos é a forma padrão pela qual os eventos se propagam no DOM. Para alterar o comportamento padrão para captura de eventos, passamos um terceiro argumento para nossos ouvintes para definir a captura como verdadeira. Se esse argumento não for passado, a captura é definida como falsa.
Veja o seguinte ouvinte de evento:
div.addEventListener('click', () => { console.log("Você clicou em um elemento div") })
Como não há um terceiro argumento, a captura está definida como falsa. Para defini-la como verdadeira, passamos o valor booleano `true`:
div.addEventListener('click', () => { console.log("Você clicou em um elemento div") }, true)
Alternativamente, podemos passar um objeto que define a propriedade `capture` como `true`, como a seguir:
div.addEventListener('click', () => { console.log("Você clicou em um elemento div") }, {capture: true})
Para testar a captura de eventos, em seu arquivo `app.js`, adicione um terceiro argumento a todos os seus ouvintes de evento, como mostrado:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Você clicou em um elemento div") }, true) const span = document.querySelector('span'); span.addEventListener('click', (e) => { console.log("Você clicou em um elemento span") }, true) const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Você clicou em um botão"); }, true)
Agora, abra o navegador e clique no botão. Você deverá ver a seguinte saída:
Note que, ao contrário do borbulhamento, onde a saída do botão é a primeira, na captura, a primeira saída vem do elemento mais externo, a `div`.
Tanto o borbulhamento quanto a captura são os principais mecanismos de propagação de eventos no DOM. No entanto, o borbulhamento é o método mais comumente utilizado.
Delegação de Eventos
A delegação de eventos é um padrão de design onde um único ouvinte de eventos é adicionado a um elemento pai comum, como um elemento `ul`, em vez de adicionar ouvintes em cada um dos seus filhos. Os eventos nos filhos “chegam” até o pai, onde são tratados pelo ouvinte do pai.
Então, em vez de adicionar ouvintes a cada filho, adicionamos um único ouvinte no pai, e este manipulador tratará todos os eventos nos filhos.
Você pode estar se perguntando como o elemento pai saberá qual filho foi clicado. Como já foi mencionado, um ouvinte passa um objeto de evento ao manipulador. Este objeto possui métodos e propriedades com informações sobre o evento. Uma dessas propriedades é o `target`, que aponta para o elemento HTML específico onde o evento ocorreu.
Por exemplo, se tivermos uma lista não ordenada com itens de lista, e adicionarmos um ouvinte ao `ul`, quando um evento acontecer em um item da lista, o `target` no objeto de evento apontará para o item de lista onde o evento ocorreu.
Para ver a delegação de eventos em ação, adicione o código HTML abaixo ao seu arquivo `index.html`:
<ul> <li>Toyota</li> <li>Subaru</li> <li>Honda</li> <li>Hyundai</li> <li>Chevrolet</li> <li>Kia</li> </ul>
E adicione o seguinte código JavaScript para usar a delegação de eventos:
const ul = document.querySelector('ul'); ul.addEventListener('click', (e) => { // Elemento alvo targetElement = e.target // Exibir o conteúdo do elemento alvo console.log(targetElement.textContent) })
Abra o navegador e clique em qualquer item da lista. O conteúdo do item clicado será exibido no console, como:
Com apenas um ouvinte, podemos tratar eventos em todos os filhos. Ter muitos ouvintes em uma página afeta o seu desempenho, consome mais memória e torna o carregamento e renderização das páginas mais lentos.
A delegação de eventos nos permite evitar tudo isso, reduzindo o número de ouvintes necessários. Ela depende do borbulhamento de eventos, o que significa que este pode ser usado para otimizar o desempenho das páginas web.
Dicas para o Tratamento Eficiente de Eventos
Como desenvolvedor, ao trabalhar com eventos, considere usar a delegação de eventos em vez de muitos ouvintes em vários elementos da página.
Ao usar a delegação, lembre-se de adicionar o ouvinte ao ancestral comum mais próximo dos filhos que precisam de tratamento de eventos. Isso ajuda a otimizar o borbulhamento e minimiza o caminho que o evento deve percorrer antes de ser tratado.
Ao manipular eventos, use o objeto de evento a seu favor. Ele contém propriedades como `target`, que são extremamente úteis.
Para melhorar o desempenho do seu site, evite manipular o DOM excessivamente. Eventos que acionam manipulações frequentes podem afetar o desempenho do seu site.
Finalmente, em elementos aninhados, tome muito cuidado ao adicionar ouvintes aninhados. Isso pode afetar o desempenho e tornar a lógica do seu código difícil de manter.
Conclusão
Os eventos são uma ferramenta poderosa em JavaScript. Borbulhamento, captura e delegação são conceitos fundamentais no tratamento de eventos. Use este artigo para se familiarizar com eles e construa sites e aplicativos mais interativos, dinâmicos e com alto desempenho.