Eventos
Eventos são ações produzidas ou "disparadas" por um sistema, como um sinal quando algum tipo de evento acontece, e provê uma forma pela qual uma ação pode ser automaticamente executada quando o evento acontece.
Por exemplo, quando estamos parados no semáforo vermelho e ele fica verde, um sinal é enviado ao motorista (através do semáforo). Como resultado, podemos seguir dirigindo.
Na web, os eventos são disparados dentro do navegador, e podem ser associados à um elemento específico, um grupo de elementos, o documento HTML, ou toda a janela do navegador.
Para reagir a um evento, associamos um event handler ao elemento. Este, é um bloco de código que executa quando o evento é disparado. Quando um bloco de código é definido para executar em resposta a um evento, estamos registrando um event handler. Também podemos chamar de event listener.
Event Objects
É um objeto passado automaticamente para o event handler para prover informações extras sobre o evento disparado. A maioria dos eventos tem propriedades e métodos padrão - ver o objeto Event para detalhes.
Alguns eventos adicionam propriedades extras que são relevantes para aquele tipo de evento. Por exemplo, o evento keydown é disparado quando o usuário pressiona uma tecla. Seu objeto é o KeyboardEvent, que adiciona a propriedade
key
com a tecla que foi pressionada.O que quero focar aqui são as propriedades target e currentTarget, ambas importantes para entendermos melhor os conceitos que serão explicados abaixo.
Target
É a propriedade que referencia o objeto em que o evento foi disparado.
<div> <button>Click me!</button> </div>
Nesse caso, ao adicionarmos um event handler na
div
e clicar no botão, a propriedade target
irá referenciar o elemento button
.É através dessa propriedade que implementos o event delegation - explicado abaixo.
Current Target
Diferente do que vimos acima, a propriedade
currentTarget
referencia o elemento em que o event handler foi associado.<div> <button>Click me!</button> </div>
Usando o mesmo exemplo, ao adicionarmos um event handler na
div
e clicar no botão, a propriedade currentTarget
irá referenciar o elemento div
.O valor da propriedade
currentTarget
só está disponível enquanto o evento estiver sendo executado. Se fizermos um console.log()
do objeto do evento, e olharmos a propriedade currentTarget
, seu valor será null
. Isso acontece pois essa propriedade é alterada durante a propagação do evento.Event Bubbling e Event Capture
São termos que descrevem fases sobre como o navegador gerencia eventos direcionados a elementos aninhados.
Considere o exemplo abaixo:
<div onclick="console.log('clicked div')"> <button>Click me!</button> </div>
Temos um botão dentro de uma div, e essa div possui um event handler para o evento onclick. O que acontece se eu clicar no botão?
Iremos perceber uma mensagem no console:
clicked div
E faz sentido! O botão está dentro da div, então quando clicamos no botão, implicitamente clicamos no elemento em que ele está dentro.
Event Bubbling
Agora, o que acontece se ambos os elementos possuírem um event handler?
<div onclick="console.log('clicked div')"> <button onclick="console.log('clicked button')">Click me!</button> </div>
Iremos perceber que ambos os elementos dispararam um evento e geraram uma mensagem no console:
clicked button clicked div
Nesse caso:
- O clique no botão disparou o evento
- O clique no elemento pai (div) disparou o próximo evento
Descrevemos esse comportamento dizendo que o evento "borbulhou" do elemento mais fundo na árvore que foi clicado
Explicando Bubbling e Capturing
Quando um evento é disparado em um elemento que possui elementos pais, os navegadores executam três fases - capturing, target, e bubbling.
Na capturing phase:
- O navegador checa se o elemento mais alto na árvore (<html>) tem um event handler para o evento onclick registrado para a capturing phase, e executa se existir.
- Então, o mesmo processo acontece para o próximo elemento depois do
html
, e assim sucessivamente até chegar ao elemento pai direto do elemento que foi clicado
Na target phase:
- O navegador checa se o target do evento tem um event handler registrado, e executa se existir.
- Então, se a propriedade bubbles for
true
, o evento é propagado para o elemento pai direto do elemento clicado, e então o próximo, e assim sucessivamente até o elementohtml
. O evento não é propagado para os elementos pai caso bubbles forfalse
Na bubbling phase, acontece o exato oposto da capturing phase:
- O navegador checa se o elemento pai direto do elemento clicado tem um event handler para o evento onclick, registrado para a bubbling phase, e executa se existir.
- Então, o mesmo processo acontece para o pai imediato desse elemento, e assim sucessivamente até o elemento
html
Nos navegadores atuais, por padrão todos os event handlers são registrados para a bubbling phase
Exemplos de bubbling e capturing
Considerando a árvore de elementos abaixo:
<body> <main id="main"> <section id="section"> <button id="button">Click</button> </section> </main> </body>
const main = document.getElementById('main') const section = document.getElementById('section') const button = document.getElementById('button') window.addEventListener('click', (eve) => { console.log('clicked window - capturing', eve.eventPhase) }, true) section.addEventListener('click', (eve) => { console.log('clicked div 2 - capturing', eve.eventPhase) }, true) main.addEventListener('click', (eve) => { console.log('clicked div 1 - bubbling', eve.eventPhase) }) section.addEventListener('click', (eve) => { console.log('clicked div 2 - bubbling', eve.eventPhase) }) button.addEventListener('click', (eve) => { console.log('clicked button - target', eve.eventPhase) })
A sequência de logs exibida no console ao clicarmos no botão será a seguinte:
clicked window - capturing clicked section - capturing clicked button - target clicked section - bubbling clicked main - bubbling
- Registramos um handler para o evento onclick no objeto
window
. Perceba que o segundo parâmetro étrue
, o que indica que registramos esse evento para a capturing phase
- O navegador varreu os elementos, identificou e executou o outro handler para a capturing phase, dessa vez do elemento
section
- Não temos mais nenhum handler para a capturing phase antes do elemento que foi clicado, então chegamos na target phase. Aqui, como o
button
possui um handler, este é executado e a partir daqui começa o processo de bubbling
- A propriedade
bubbles
é true, então o evento começa a ser propagado para o pai direto do botão, que é o elementosection
. Temos um evento handler para a bubbling phase, então este é executado.
- O elemento
main
é o pai direto dosection
, então, o seu handler também é executado, e como não temos mais nenhum event handler para a bubbling phase, o ciclo se encerra.
Podemos alterar o código de forma a termos ordens de execução diferentes para os eventos. Por exemplo, se usarmos o método
stopPropagation
no handler do button
, o resultado será o seguinte:clicked window - capturing clicked section - capturing clicked button - target
Interrompemos a propagação do evento a partir do
button
, logo, os handlers dos elementos section
e main
, registrados para a bubbling phase, não foram executados.Por outro lado, se usarmos o método
stopPropagation
no handler do window
, o resultado será o seguinte:clicked window - capturing
Nenhum outro handler será executado, já que interrompemos a propagação a partir do nível mais alto dos handlers registrados para a capturing phase, que é a primeira a ser executada.
Event Delegation
Através do event bubbling é possível utilizar de uma prática chamada event delegation.
Quando queremos executar um código a partir da interação do usuário em um grande número de elementos filhos, podemos associar o event handler no pai desses elementos, e então os eventos que acontecem nos filhos propagam para o pai. Dessa forma evitamos usar um event handler para cada filho individualmente.
<div id="container" onclick="console.log('clicked')"> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> </div>
Dessa forma, garantimos que o event bubbling irá executar o event handler sempre que o usuário clicar em um filho.