Domine o Borbulhamento de Eventos em JavaScript: Guia Completo para Web Dinâmica

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.