A chave oculta para interações dinâmicas na Web

Quando eu ainda estava aprendendo os fundamentos do desenvolvimento web, um dos comportamentos surpreendentes e intrigantes que encontrei foi o borbulhamento de eventos. A princípio parecia incomum, mas quando você pensa em eventos borbulhando, você verá que faz muito sentido. Como desenvolvedor web, você certamente encontrará eventos borbulhantes. Então, o que o evento está borbulhando?

Para fornecer aos usuários a capacidade de interagir com páginas da web, o JavaScript depende de eventos. Um evento refere-se a ocorrências ou ações que podem ser detectadas e respondidas pelo código que você escreve. Exemplos de eventos incluem cliques do mouse, pressionamentos de teclas e envio de formulários, entre outros.

JavaScript usa ouvintes de eventos para detectar e responder a eventos. Um ouvinte de evento é uma função que escuta ou espera que um evento específico ocorra em uma página. Por exemplo, um evento de clique em um botão. Quando um ouvinte de evento detecta o evento que está escutando, ele responde executando o código associado ao evento. Todo esse processo de captura e resposta a eventos é chamado de manipulação de eventos.

Agora imagine que temos três elementos em uma página: um div, um span e um botão. O elemento button está aninhado dentro do elemento span e o elemento span está aninhado dentro do div. Uma ilustração disso é mostrada abaixo:

Supondo que cada um desses elementos tenha um ouvinte de evento que escuta um evento de clique e imprime no console quando clicado, o que acontece quando você clica no botão?

Para testar você mesmo, crie uma pasta e, na pasta, crie um arquivo HTML chamado index.html, um arquivo CSS chamado style.css e um arquivo JavaScript chamado app.js.

No arquivo HTML, adicione o seguinte código:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Event bubbling</title>
  <link rel="stylesheet" href="https://wilku.top/the-hidden-key-to-dynamic-web-interactions/style.css">
</head>

<body>
  <div>
    <span><button>Click Me!</button></span>
  </div>
  <script src="app.js"></script>
</body>

</html>

No arquivo CSS, adicione o código a seguir para estilizar o elemento div e o elemento 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;
}

No arquivo JavaScript, adicione o código a seguir, que adiciona ouvintes de eventos aos elementos div, span e button. Todos os listers de eventos estão atentos a um evento de clique.

const div = document.querySelector('div');
div.addEventListener('click', () => {
  console.log("You've clicked a div element")
})

const span = document.querySelector('span');
span.addEventListener('click', () => {
  console.log("You've clicked a span element")
})

const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button")
})

Agora abra o arquivo HTML em um navegador. Inspecione a página e clique no botão na página. O que você percebe? A saída de clicar no botão é mostrada abaixo:

Clicar no botão aciona o ouvinte de eventos que escuta um evento de clique no botão. No entanto, os ouvintes de eventos nos elementos span e div também são acionados. Por que é esse o caso, você pode perguntar.

Clicar no botão aciona o ouvinte de evento anexado ao botão, que é impresso no console. Porém, como o botão está aninhado dentro de um elemento span, clicar no botão significa que, tecnicamente, também estamos clicando no elemento span e, portanto, seu ouvinte de evento é acionado.

Como o elemento span também está aninhado dentro do elemento div, clicar no elemento span também significa que o elemento div também é clicado e, como resultado, seu ouvinte de evento também é acionado. Isso é um evento borbulhante.

Borbulhamento de eventos

O borbulhamento de eventos é o processo pelo qual um evento acionado em um conjunto aninhado de elementos HTML é propagado ou borbulha a partir do elemento mais interno onde foi acionado e sobe pela árvore DOM até o elemento raiz, acionando todos os ouvintes de eventos que escutam esse evento.

  Como economizar dinheiro no armazenamento iCloud

Os ouvintes de eventos são acionados em uma ordem específica que corresponde à forma como o evento borbulha ou se propaga na árvore DOM. Considere a árvore DOM mostrada abaixo, que representa a estrutura do HTML usado neste artigo.

Um evento de clique borbulhando na árvore DOM

A árvore DOM mostra um botão, aninhado dentro de um span, que está aninhado dentro de um div, que está aninhado dentro do corpo e o corpo está aninhado dentro do elemento HTML. Como os elementos foram aninhados uns dentro dos outros quando você clica no botão, o evento click acionará o ouvinte de eventos anexado ao botão.

No entanto, como os elementos estão aninhados, o evento subirá na árvore DOM (bolha para cima) para o elemento span, depois para o div, depois para o corpo e, finalmente, para o elemento HTML, acionando todos os ouvintes de eventos que escutam um evento de clique nesse ordem.

É por isso que o ouvinte de evento anexado aos elementos span e div é executado. Se tivéssemos ouvintes de eventos ouvindo um clique no corpo e no elemento HTML, eles também teriam sido acionados.

O nó DOM onde ocorre um evento é chamado de destino. No nosso caso, como o clique acontece no botão, o elemento button é o alvo do evento.

Como parar o borbulhamento de eventos

Para impedir que um evento surja no DOM, usamos um método chamado stopPropagation() que está disponível no objeto de evento. Considere o exemplo de código abaixo, que usamos para adicionar um ouvinte de evento ao elemento botão.

const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button");
})

O código leva a um evento que surge na árvore DOM quando um usuário clica no botão. Para evitar bolhas de eventos, chamamos o método stopPropagation() conforme mostrado abaixo:

const button = document.querySelector('button');
button.addEventListener('click', (e) => {
  console.log("You've clicked a button");
  e.stopPropagation();
})

Um manipulador de eventos é a função executada quando um botão é clicado. Um ouvinte de eventos passa automaticamente um objeto de evento para um manipulador de eventos. No nosso caso, este objeto de evento é representado pelo nome da variável e, que é passado como parâmetro no manipulador de eventos.

Este objeto de evento,e, contém informações sobre um evento e também nos dá acesso a uma variedade de propriedades e métodos relacionados a eventos. Um desses métodos é stopPropagation(), que é usado para evitar bolhas de eventos. Chamar stopPropagation() no ouvinte de eventos do botão evita que um evento surja na árvore DOM a partir do elemento do botão.

  Ajuste o foco depois de tirar uma foto no seu iPhone

O resultado de clicar no botão após adicionar o método stopPropagation() é mostrado abaixo:

Usamos stopPropagation() para evitar que um evento surja no elemento em que o usamos. Por exemplo, se quiséssemos que o evento click subisse do elemento button até o elemento span e não borbulhasse ainda mais na árvore DOM, usaríamos stopPropagation() no ouvinte de evento do span.

Captura de eventos

A captura de eventos é o oposto do borbulhamento de eventos. Na captura de eventos, um evento desce do elemento mais externo para o elemento de destino, conforme ilustrado abaixo:

Evento de clique chegando ao elemento de destino devido à captura de eventos

Por exemplo, no nosso caso, quando você clica no elemento botão, na captura de eventos, os ouvintes de eventos no elemento div serão os primeiros a serem acionados. Os ouvintes no elemento span seguem e, finalmente, os ouvintes no elemento de destino serão acionados.

O borbulhamento de eventos é, no entanto, a forma padrão pela qual os eventos são propagados no Document Object Model (DOM). Para alterar o comportamento padrão de evento borbulhante para captura de evento, passamos um terceiro argumento para nossos ouvintes de evento para definir a captura de evento como verdadeira. Se você não passar um terceiro argumento para os ouvintes de eventos, a captura de eventos será definida como falsa.

Considere o ouvinte de evento abaixo:

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
})

Como não possui terceiro argumento, a captura é definida como falsa. Para definir a captura como verdadeira, passamos um terceiro argumento, o valor booleano verdadeiro, que define a captura como verdadeira.

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, true)

Alternativamente, você pode passar um objeto que defina capture como true, conforme mostrado abaixo:

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, {capture: true})

Para testar a captura de eventos, em seu arquivo JavaScript, adicione um terceiro argumento a todos os seus ouvintes de eventos, conforme mostrado:

const div = document.querySelector('div');
div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, true)

const span = document.querySelector('span');
span.addEventListener('click', (e) => {
  console.log("You've clicked a span element")
}, true)

const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button");
}, true)

Agora abra seu navegador e clique no elemento botão. Você deve obter essa saída:

Observe que, diferentemente do evento borbulhando, onde a saída do botão foi impressa primeiro, na captura de eventos, a primeira saída vem do elemento mais externo, o div.

O borbulhamento e a captura de eventos são as principais formas pelas quais os eventos são propagados no DOM. No entanto, o borbulhamento de eventos é comumente usado para propagar eventos.

Delegação de Eventos

A delegação de eventos é um padrão de design onde um único ouvinte de eventos é anexado a um elemento pai comum, por exemplo, um elemento

    , em vez de ter ouvintes de eventos em cada um dos elementos filhos. Os eventos nos elementos filhos chegam ao elemento pai, onde são manipulados pelo manipulador de eventos no elemento pai.

    Portanto, no caso em que temos um elemento pai com elementos filhos dentro dele, apenas adicionamos um ouvinte de evento ao elemento pai. Este manipulador de eventos tratará todos os eventos nos elementos filhos.

    Você deve estar se perguntando como o elemento pai saberá qual elemento filho foi clicado. Conforme mencionado anteriormente, um ouvinte de evento passa um objeto de evento para um manipulador de eventos. Este objeto de evento possui métodos e propriedades que oferecem informações sobre um determinado evento. Uma das propriedades no objeto de evento é a propriedade target. A propriedade target aponta para o elemento HTML específico onde ocorreu um evento.

    Por exemplo, se tivermos uma lista não ordenada com itens de lista e anexarmos um ouvinte de evento ao elemento

      quando um evento ocorrer em um item da lista, a propriedade target no objeto de evento apontará para o item de lista específico onde o evento ocorreu.

      Para ver a delegação de eventos em ação, adicione o seguinte código HTML ao arquivo HTML existente:

      <ul>
          <li>Toyota</li>
          <li>Subaru</li>
          <li>Honda</li>
          <li>Hyundai</li>
          <li>Chevrolet</li>
          <li>Kia</li>
        </ul>

      Adicione o seguinte código JavaScript para usar a delegação de eventos para usar um único ouvinte de eventos em um elemento pai para escutar eventos em elementos filhos:

      const ul = document.querySelector('ul');
      ul.addEventListener('click', (e) => {
        // target element
        targetElement = e.target
        // log out the content of the target element
        console.log(targetElement.textContent)
      })

      Agora abra o navegador e clique em qualquer item da lista. O conteúdo do elemento deve ser impresso no console conforme mostrado abaixo:

      Usando um único ouvinte de eventos, podemos lidar com eventos em todos os elementos filhos. Ter tantos ouvintes de eventos em uma página afeta seu desempenho, consome mais memória e leva ao carregamento e renderização lentos das páginas.

      A delegação de eventos nos permite evitar tudo isso, minimizando o número de ouvintes de eventos que precisamos usar em uma página. A delegação de eventos depende do borbulhamento de eventos. Portanto, podemos dizer que o evento borbulhante pode ajudar a otimizar o desempenho das páginas web.

      Dicas para tratamento eficiente de eventos

      Como desenvolvedor, ao trabalhar com eventos no modelo de objeto de documento, considere usar a delegação de eventos em vez de ter muitos ouvintes de eventos em elementos da sua página.

      Ao usar a delegação de eventos, lembre-se de anexar um ouvinte de evento ao ancestral comum mais próximo dos elementos filhos que precisam de manipulação de eventos. Isso ajuda a otimizar o borbulhamento de eventos e também minimiza o caminho que um evento deve percorrer antes de ser tratado.

      Ao manipular eventos, use o objeto de evento fornecido pelo ouvinte de eventos para sua vantagem. O objeto de evento contém propriedades como target, que são úteis no tratamento de eventos.

      Para ter sites com melhor desempenho, evite a manipulação excessiva do DOM. Ter eventos que desencadeiam manipulações frequentes de DOM pode afetar negativamente o desempenho do seu site.

      Finalmente, no caso de elementos aninhados, tenha muito cuidado ao anexar ouvintes de eventos aninhados aos elementos. Isso pode não apenas afetar o desempenho, mas também tornará o tratamento de eventos muito complexo e será difícil manter seu código.

      Conclusão

      Os eventos são uma ferramenta poderosa em JavaScript. Borbulhamento de eventos, captura de eventos e delegação de eventos são ferramentas importantes no tratamento de eventos em JavaScript. Como desenvolvedor web, use o artigo para se familiarizar com os conceitos para que você possa construir sites e aplicativos mais interativos, dinâmicos e de alto desempenho.