Всплытие и перехват событий

Все интернет-страницы состоят из отдельных элементов (тегов). При выводе станицы на экран браузер создаёт DOM-дерево, отражающее структуру документа (связи между родительскими и дочерними элементами).

В DOM-модели каждый дочерний элемент является частью родительского элемента. Следовательно, если на элементе возникает какое-либо событие (например, клик мышью), то оно же возникает и на его родителе, и так далее вверх по DOM-дереву вплоть до корневого элемента (объект document). Если нескольким из таких элементов будут назначены обработчики события, то все они выполнятся.

В примере ниже клик мышью по элементу <mark> вызовет обработчик onclick и на родительском элементе <div>.

<html>
<head>
  <title>События</title>
</head>
<body>
  <div onclick="alert('Обработчик DIV')">
    <mark onclick="alert('Обработчик MARK')">MARK</mark> внутри DIV
  </div>
</body>
</html>
MARK внутри DIV

Стадии обработки события

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

Само исходное событие для всех обработчиков является общим. Его обработка делится на три стадии:

  • Стадия перехвата (capturing stage).
  • Стадия цели (target stage).
  • Стадия всплытия (bubbling stage).

Элемент, на котором возникает событие, называется целевым.

Стадия перехвата

При возникновении события браузер поочерёдно проходит от верхнего элемента DOM-дерева (document) вниз через все промежуточные элементы к целевому. Эта стадия обработки называется стадией перехвата. Обработчики запускаются до того, как событие дойдёт до целевого элемента. Событие как бы перехватывается. Отсюда и следует такое название стадии.

Обработчики события выполняются только в том случае, если для них задано выполнение на стадии перехвата. Для этого необходимо использовать значение true для третьего атрибута метода addEventListener(). Это единственный способ использовать обработчик на стадии перехвата.

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

Стадия цели

После того, как событие опустилось до целевого элемента, стадия перехвата завершается и выполняются обработчики целевого элемента. Это вторая стадия - стадия цели. Очерёдность выполнения обработчиков на целевом элементе зависит только от очерёдности их назначения. Использование третьего аргумента метода addEventListener() никак не повлияет на порядок их запуска.

Стадия всплытия

Далее начинается последняя стадия. Событие проходит поочерёдно от целевого элемента через цепочку родителей до корневого элемента документа. Событие продвигается вверх по DOM-дереву. Оно будто всплывает. Отсюда и название - стадия всплытия.

На данной стадии выполняются все остальные обработчики. Обработчики, назначенные с помощью атрибутов событий и DOM-свойств всегда выполняются на стадии всплытия. В методе addEventListener() для использования стадии всплытия можно просто опустить третий аргумент.

Прохождение всех стадий продемонстрировано на примере ниже (запускается кликом по жёлтому элементу):

<html>
<head>
  <title>События</title>
  <style>
    div {
      width: 60px;
      height: 100px;
      position: relative;
      background: red;
    }
    aside {
      width: 80px;
      height: 60px;
      background: yellow;
      position: absolute;
      top: 20px;
      left: 70px;
    }
  </style>
</head>
<body>
  <div>DIV
    <aside>ASIDE</aside>
  </div>
</body>
</html>

<script>
  var div = document.getElementsByTagName('div')[0];
  var aside = document.getElementsByTagName('aside')[0];
  div.addEventListener('click', function() {alert('DIV - перехват')}, true);
  div.addEventListener('click', function() {alert('DIV - всплытие')});
  aside.addEventListener('click', function() {alert('ASIDE - всплытие')});
  aside.addEventListener('click', function() {alert('ASIDE - перехват')}, true);
</script>
DIV

Обработчики элемента <aside> запускаются в порядке назначения.

В этом примере наглядно видно, что событие всегда всплывает по дереву DOM, несмотря на то, что визуально событие не произошло над элементом <div>.