Front End

6 out, 2014

JavaScript bubbling e capturing

Publicidade

01

Elementos da marcação HTML podem ser aninhados uns dentro de outros, criando-se uma cadeia de elementos-filhos e seus elementos-ancestrais. Neste cenário, quando se atrela um evento JavaScript (por exemplo, o evento clicar) a um determinado elemento E do DOM ele, evento, será disparado mesmo que o clique ocorre em um elemento-filho de E.

O nome usado na terminologia JavaScript para descrever esse efeito é event bubbling. Bubbling em tradução livre significa borbulhante, assim em linguagem não-técnica podemos dizer que eventos JavaScript borbulham, no sentido de que são disparados por ação em elementos descendentes do elemento a que são atrelados.

No exemplo mostrado a seguir o evento clique dispara o alerta mesmo quando o clique ocorre nos elementos em ou code, embora ele, evento, tenha sido atrelado ao elemento div.

<div onclick="alert('Evento disparado!')">
<em>Nesta área experimente clicar no elemento  <code>EM</code> aninhado não clicando no <code>DIV</code> e observe que há o disparo do alerta.</em>
</div>

Esse efeito ocorre porque o evento “borbulha” bubbles do elemento mais aninhado para seu elemento-ancestral.

Bubbling

O princípio fundamental do efeito bubbling diz o seguinte: depois que um evento é disparado no elemento mais distante de uma cadeia aninhada do DOM ele é disparado em seus elementos ancestrais na ordem crescente de aninhamento.

Observe um exemplo com três elementos div aninhados:

<!DOCTYPE HTML>
<html>
<body>
<link type="text/css" rel="stylesheet" href="example.css">
    <div class="d1">1  <!-- ancestral mais alto -->
        <div class="d2">2
            <div class="d3">3 <!-- descendente mais baixo -->
            </div>
        </div>
    </div>
</body>
</html>

02

O efeito bubbling da JavaScript faz com que um clique no no div 3 dispare o evento a ele atrelado primeiro no elemento descendente mais baixo 3 (também chamado de target), depois no elemento 2 e finalmente no elemento 1.

– um clique no no div 2 dispara o evento a ele atrelado primeiro no elemento 2 (também chamado de target) e depois no elemento 1.

– um clique no no div 1 dispara o evento a ele atrelado (também chamado de target) e nada mais.

03

A ordem de disparo é chamada de bubbling order, pois o evento “borbulha” do elemento descendente mais baixo para seus ancestrais tal como ocorre com uma bolha de ar na água.

O exemplo mostrado a seguir é interativo e demonstra visualmente o efeito bubble. Clique os divs:

Observe a seguir os código do exemplo:

HTML

<div class="d1">1  
    <div class="d2">2
        <div class="d3">3 
        </div> 
    </div>
</div>

CSS

<style>
.d1 {
  background-color: green;
  position: relative;
  width: 150px;
  height: 150px;
  text-align: center;
  cursor: pointer;
}

.d2 {
  background-color: blue;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 100px;
  height: 100px;
}

.d3 {
  background-color: red;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 50px;
  height: 50px;
  line-height: 50px;
}
</style>

JavaScript

<script>
var divs = document.getElementsByTagName('div')

for(var i=0; i<divs.length; i++) {
  divs[i].onclick = function(e) {
    e = e || event
    var target = e.target || e.srcElement

    this.style.backgroundColor='yellow'
    
    alert("target = "+target.className+", this="+this.className)

    this.style.backgroundColor = ''
  }
}
</script>

this e event.target

O elemento descendente mais baixo que dispara o evento é chamado de target ou elemento originário.

O navegador Internet Explorer define para target a propriedade srcElement e os navegadores em conformidade com o W3C definem a propriedade event.target.

O código JavaScript cross-browser é mostrado a seguir:

var target = event.target || event.srcElement

Handlers (ações disparadas por eventos) em ancestrais:

  • event.target/srcElement – refere-se ao elemento que origina o evento.
  • this – refere-se ao elemento corrente, aquele para o qual o evento “borbulhou” ou ainda, aquele que dispara o handler.

04

No exemplo interativo mostrado a seguir, para cada elemento div foi definido o atributo onclick para disparar um handler cuja saída mostra quem é target e quem é this.

Clique em um div e observe o seguinte:

  • target permanece constante durante todo o processo de bubbling,
  • this modifica-se e é destacado em cor diferente.

Observe a seguir os código do exemplo:

HTML

<div class="d1">1  
    <div class="d2">2
        <div class="d3">3 
        </div> 
    </div>
</div>

CSS

<style>
.d1 {
  background-color: green;
  position: relative;
  width: 150px;
  height: 150px;
  text-align: center;
  cursor: pointer;
}

.d2 {
  background-color: blue;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 100px;
  height: 100px;
}

.d3 {
  background-color: red;
  position: absolute;
  top: 25px;
  left: 25px;
  width: 50px;
  height: 50px;
  line-height: 50px;
}
</style>

JavaScript

<script>
var divs = document.getElementsByTagName('div')

for(var i=0; i<divs.length; i++) {
  divs[i].onclick = function(e) {
    e = e || event
    var target = e.target || e.srcElement

    this.style.backgroundColor='yellow'
    
    alert("target = "+target.className+", this="+this.className)

    this.style.backgroundColor = ''
  }
}
</script>

Em navegadores em conformidade com o W3C, this também pode ser obtido com uso de event.currentTarget.

Cancelar bubbling

Vimos que o efeito bubbling percorre elementos aninhados no DOM de baixo para cima (do elemento filho para seus ancestrais).

É possível interromper o efeito bubbling antes que ele percorra todos os elementos aninhados.

O código para interromper o efeito em navegadores em conformidade com o W3C é mostrado a seguir:

event.stopPropagation()

Para os IE<9:

event.cancelBubble = true

E, finalmente o código cross-browser:

element.onclick = function(event) {
event = event || window.event // cross-browser event
  if (event.stopPropagation) {
    // opção para poadrões W3C
    event.stopPropagation()
  } else {
   // opção para IE
  event.cancelBubble = true
 }
}

Simplificação do código cross-browser:

event.stopPropagation ? event.stopPropagation() : (event.cancelBubble=true)

Se a um elemento for atrelado vários handlers disparados pelo mesmo evento, os handelers serão independentes..

Por exemplo: se em um link existerem dois handelers disparados por click, interromper o efeito bubbling em um dos handelers não interrompe no outro. O navegador não tem qualquer compromisso com a ordem de disparo dos handelers.

Capturing

Em todos os navegadores, exceto nos IE<9 os eventos são processados em dois estágios.

No primeiro estágio o evento percorre a cadeia aninhada de cima para baixo (dos elementos ancestrais para os elementos descendentes) – este estágio é chamado capturing. No outro estágio ocorre o efeito bubbles como estudado. Este comportamento é padronizado pelas especificações do W3C.

05

Segundo esse modelo o comportamento do evento é:

  • Captures para baixo – na direção 1 -> 2 -> 3.
  • Bubbles para cima – na direção 3 -> 2 -> 1.

Todos os métodos de manipulação de eventos simplesmente ignoram a fase caputuring. Para que o evento ocorra na fase capturing declaramos o último argumento método addEventListener como sendo true.

Observe o código mostrado a seguir:

elem.addEventListener( type, handler, phase )
  • phase = true – o handler dispara na fase capturing.
  • phase = false – o handler dispara na fase bubbling.

Clique em um dos divs mostrados a seguir para constatar o efeito capturing em ação (exceto IE<9):

A ordem deverá ser 1 -> 2 -> 3. Observe o código JavaScript desse exemplo:

var divs = document.getElementsByTagName('div')
    for(var i=0; i<divs.length; i++) {
        divs[i].addEventListener("click", highlightThis, true)
    }

Na prática a fase capturing raramente é usada, mas existem eventos que não “borbulham”, mas honram o efeito capturing. Por exemplo: onfocus e onblur.

No exemplo mostrado a seguir atrelamos handlers a ambos os estágios.

Clique em um dos divs mostrados a seguir para constatar a ordem de processamento dos eventos (exceto IE<9):

A ordem deverá ser 1 -> 2 -> 3 -> 3 -> 2 -> 1.

Observe o código JavaScript desse exemplo:

var divs = document.getElementsByTagName('div')
for(var i=0; i<divs.length; i++) {
divs[i].addEventListener("click", highlightThis, true)
divs[i].addEventListener("click", highlightThis, false)
}

Sumário

  • Eventos primeiramente são captured para baixo e depois bubble para cima. Os IE<9 honram apenas o efeito bubble.
  • Todos os handlers disparam no efeito bubbling exceto quando o último argumento do método addEventListener for declarado true, aliás essa é a única maneira de disparar o evento na fase capturing.
  • Bubbling e capturing podem ser cancelados no IE com uso de event.cancelBubble=true (IE) ou com event.stopPropagation() para os demais navegadores.

Crédito: http://learn.javascript.ru/
Publicado segundo os termos da licença CC BY-NC-SA.