Когда некоторый объект инициирует событие, то оно не просто возникает на нём, а распространяется в документе определённым образом.

Согласно стандарту, оно делится на 3 фазы:

  1. Фаза погружения или захвата – от window к родителю цели (цель – это объект, который инициировал это событие).
  2. Фаза цели – событие на цели.
  3. Фаза всплытия – обратно, от родителя цели к window.

Самое главное для нас, когда событие путешествует по документу, то браузер вызывает обработчики элементов, через которые оно проходит.

Фаза погружения события

Всплытие

Когда на элементе происходит событие, обработчики сначала срабатывают на нём, потом на его родителе, затем выше и так далее, вверх по цепочке предков.

Например, есть 3 вложенных элемента FORM > DIV > P с обработчиком на каждом:

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>
 
<form onclick="alert('form')">
  FORM
  <div onclick="alert('div')">
    DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>
`

Клик по внутреннему <p> вызовет обработчик onclick:

  1. Сначала на самом <p>.
  2. Потом на внешнем <div>.
  3. Затем на внешнем <form>.
  4. И так далее вверх по цепочке до самого document.

Этот процесс называется «всплытием», потому что события «всплывают» от внутреннего элемента вверх через родителей подобно тому, как всплывает пузырёк воздуха в воде.

Погружение

Существует ещё одна фаза из жизненного цикла события – погружение (иногда её называют «перехват»). Она очень редко используется в реальном коде, однако тоже может быть полезной.

Стандарт DOM Events описывает 3 фазы прохода события:

  1. Фаза погружения (capturing phase) – событие сначала идёт сверху вниз.
  2. Фаза цели (target phase) – событие достигло целевого(исходного) элемента.
  3. Фаза всплытия (bubbling stage) – событие начинает всплывать.

То есть при клике на <td> событие путешествует по цепочке родителей сначала вниз к элементу (погружается), затем оно достигает целевой элемент (фаза цели), а потом идёт наверх (всплытие), вызывая по пути обработчики.

Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.

Обработчики, добавленные через on<event>-свойство или через HTML-атрибут , или через addEventListener( event, handler) с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах.

Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент capture вот так:

elem.addEventListener(..., {capture: true}) // или просто "true", как сокращение для {capture: true}
elem.addEventListener(..., true)`

Существуют два варианта значений опции capture:

  • Если аргумент false (по умолчанию), то событие будет поймано при всплытии.
  • Если аргумент true, то событие будет перехвачено при погружении.

Обратите внимание, что хоть и формально существует 3 фазы, 2-ую фазу («фазу цели»: событие достигло элемента) нельзя обработать отдельно, при её достижении вызываются все обработчики: и на всплытие, и на погружение.

Давайте посмотрим и всплытие и погружение в действии:

<style>
	body * {
		margin: 10px;
		border: 1px solid blue;   }
</style>
 
<form>FORM
	<div>DIV
		<p>P</p>
	</div>
</form>
 
<script>
	for(let elem of document.querySelectorAll('*')) {
		elem.addEventListener("click", e => alert(`Погружение: ${elem.tagName}`), true);
		elem.addEventListener("click", e => alert(`Всплытие: ${elem.tagName}`));   }
</script>``

Здесь обработчики навешиваются на каждый элемент в документе, чтобы увидеть в каком порядке они вызываются по мере прохода события.

Если вы кликните по <p>, то последовательность следующая:

  1. HTML → BODY → FORM → DIV (фаза погружения, первый обработчик)
  2. P (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза)
  3. DIV → FORM → BODY → HTML (фаза всплытия, второй обработчик)

Существует свойство event.eventPhase, содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике.

Чтобы убрать обработчик removeEventListener, нужна та же фаза

Если мы добавили обработчик вот так addEventListener(..., true), то мы должны передать то же значение аргумента capture в removeEventListener(..., true), когда снимаем обработчик.

На каждой фазе разные обработчики на одном элементе срабатывают в порядке назначения

Если у нас несколько обработчиков одного события, назначенных addEventListener на один элемент, в рамках одной фазы, то их порядок срабатывания – тот же, в котором они установлены:

elem.addEventListener("click", (e) => alert(1)) // всегда сработает перед следующим elem.addEventListener("click", e => alert(2));`

Назад