W3docs

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

Всплытие и перехват событий — две фазы модели распространения событий в DOM, возникающие при их срабатывании.

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

Когда вы нажимаете кнопку, событие клика не ограничивается только этой кнопкой — оно проходит через все элементы-предки на пути к цели и обратно. Это движение называется распространением события, и оно происходит в двух направлениях: перехват (вниз к целевому элементу) и всплытие (обратно вверх к корню). Понимание обеих фаз необходимо для надёжной обработки событий в реальных приложениях, особенно когда вложенные элементы имеют собственные обработчики.

В этом руководстве объясняется модель распространения, показано, как слушать события в каждой фазе, и рассматриваются практические инструменты — event.target, event.currentTarget, stopPropagation() и делегирование событий, — которые делают эти концепции полезными в повседневной работе. Материал опирается на основы, изложенные в разделах Введение в события браузера и События JavaScript.

Понимание распространения событий

Распространение события в DOM происходит в трёх фазах, в строго определённом порядке:

  1. Фаза перехвата — событие начинается с вершины дерева (windowdocument<html> → …) и движется вниз к целевому элементу.
  2. Целевая фаза — событие достигает элемента, с которым вы непосредственно взаимодействовали.
  3. Фаза всплытия — событие движется обратно вверх от цели к корню.
                 │ capturing (down)    ▲ bubbling (up)
   <html>        ▼                     │
     <div>       ▼                     │
       <p> ───►  target (you clicked here)

По умолчанию обработчики, добавленные с помощью addEventListener и встроенных атрибутов on*, выполняются в фазе всплытия. Для фазы перехвата нужно явно указать соответствующую опцию.

Всплытие событий

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

<div onclick="alert('You clicked the DIV!');">
  Click me or one of my children:
  <p onclick="alert('You clicked the P!');">Click me!</p>
</div>

Если вы нажмёте на <p>, сначала появится предупреждение для <p>, затем — для <div> по мере всплытия события. Если вы нажмёте непосредственно на <div> (за пределами <p>), сработает только предупреждение <div> — событие никогда не достигнет <p>, поскольку <p> не является предком точки клика.

Перехват событий

Перехват — это первая фаза, в которой событие движется вниз к цели. Она используется значительно реже, чем всплытие, но удобна в тех случаях, когда нужно перехватить событие до того, как сработает любой внутренний обработчик.

Чтобы слушать событие в фазе перехвата, передайте третьим аргументом addEventListener значение true (или объект { capture: true }):

<div id="outer">
  Click me or one of my children:
  <p id="inner">Click me!</p>
</div>

<script>
  document.getElementById('outer').addEventListener('click', function () {
    alert('Captured on DIV!');
  }, true); // true → capturing phase

  document.getElementById('inner').addEventListener('click', function () {
    alert('Captured on P!');
  }, true);
</script>

Нажмите на <p> — предупреждения сработают сверху вниз: сначала DIV (предок, через который проходит событие на пути вниз), затем P (цель). При обработчиках всплытия порядок был бы обратным.

Определение нужного элемента

Внутри обработчика вам, как правило, нужно знать, на каком элементе событие возникло и к какому элементу прикреплён обработчик. На эти вопросы отвечают два свойства:

  • event.target — элемент, на котором событие возникло (самый глубокий из нажатых). Остаётся неизменным на протяжении всего распространения.
  • event.currentTarget — элемент, чей обработчик выполняется в данный момент. Меняется по мере движения события через дерево и равен this внутри обычного функционального обработчика.
function logTargets(event) {
  console.log("target:", event.target.tagName);
  console.log("currentTarget:", event.currentTarget.tagName);
}
// Imagine this handler is on a <div> and you click a nested <p>:
// target:        P    (where the click happened)
// currentTarget: DIV  (where the listener lives)

Именно event.target делает возможным делегирование событий (показано ниже) — один обработчик на родительском элементе может точно определить, на каком дочернем элементе произошёл клик.

Управление распространением

JavaScript предоставляет несколько методов для контроля над тем, как далеко распространяется событие.

МетодЭффект
event.stopPropagation()Останавливает движение события к следующему элементу в пути (дальнейшее всплытие/перехват прекращается). Обработчики на том же элементе продолжают выполняться.
event.stopImmediatePropagation()Останавливает распространение и предотвращает запуск других обработчиков на том же элементе.
event.preventDefault()Отменяет действие браузера по умолчанию (например, переход по ссылке). Распространение не останавливает.

stopPropagation() и preventDefault() независимы друг от друга. Остановка распространения не отменяет действие по умолчанию, и наоборот.

О событиях, которые не всплывают

Большинство событий всплывают, но некоторые — нет: например, focus, blur, mouseenter, mouseleave и load. Для них нельзя рассчитывать на то, что родительский обработчик перехватит их через всплытие; используйте аналоги с поддержкой всплытия (focusin/focusout, mouseover/mouseout) или прикрепляйте обработчик непосредственно к элементу. Вы всегда можете проверить event.bubbles, чтобы убедиться, участвует ли данное событие в фазе всплытия.

Практические примеры

Пример 1: Остановка всплытия события

Иногда требуется, чтобы клик по дочернему элементу не запускал обработчик родителя:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Propagation Example</title>
<style>
  .container {
    width: 200px;
    height: 200px;
    background-color: lightblue;
    padding: 20px;
  }

  .box {
    width: 100px;
    height: 100px;
    background-color: pink;
    margin-top: 20px;
    cursor: pointer;
  }
</style>
</head>
<body>

<div class="container" onclick="alert('You clicked the container!');">
  Click the pink box to see event propagation:
  <div class="box" onclick="event.stopPropagation(); alert('You clicked the box without bubbling!');"></div>
</div>

</body>
</html>

В этом примере есть контейнер с голубым фоном, внутри которого находится розовый блок. Клик в любом месте контейнера вызывает предупреждение «You clicked the container!». Однако клик по розовому блоку вызывает другое предупреждение — «You clicked the box without bubbling!» — потому что event.stopPropagation() предотвращает всплытие события клика до контейнера.

Пример 2: Использование всплытия и перехвата одновременно

Этот пример показывает, как обрабатывать событие одновременно в фазах перехвата и всплытия:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Event Capture and Bubbling Example</title>
<style>
  body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 20px;
  }

  #outerContainer {
    border: 2px solid #ccc;
    padding: 20px;
    margin-bottom: 20px;
    background-color: #f9f9f9;
    border-radius: 10px;
  }

  #innerElement {
    background-color: #ffa8a8;
    padding: 10px;
    border-radius: 5px;
    cursor: pointer;
  }
</style>
</head>
<body>

<div id="outerContainer" onclick="alert('Event Bubbled from Outer Container');">
  <p style="margin: 0;">Click anywhere in this outer container:</p>
  <p id="innerElement">Click me!</p>
</div>

<script>
  // Event listener attached to the outer container during the capturing phase
  document.getElementById('outerContainer').addEventListener('click', function() {
    alert('Event Captured by Outer Container');
  }, true);

  // Event listener attached to the inner element during the bubbling phase
  document.getElementById('innerElement').addEventListener('click', function() {
    alert('Event Bubbled from Inner Element');
  }, false);
</script>

</body>
</html>

При клике по внутреннему элементу:

  1. Сначала срабатывает обработчик перехвата на #outerContainer, выводя предупреждение "Event Captured by Outer Container" (он находится на пути вниз к цели).
  2. Затем срабатывает обработчик всплытия на #innerElement, выводя предупреждение "Event Bubbled from Inner Element" (сам целевой элемент).
  3. Наконец, встроенный onclick на #outerContainer срабатывает по мере всплытия события, выводя предупреждение "Event Bubbled from Outer Container".

Это делает полный путь распространения наглядным: перехват вниз, достижение цели, затем всплытие обратно вверх.

Пример 3: Делегирование событий

Наиболее распространённое практическое применение всплытия — делегирование событий: прикрепление единственного обработчика к родителю вместо отдельного обработчика для каждого дочернего элемента. Поскольку клики всплывают, родитель может использовать event.target, чтобы определить, по какому дочернему элементу щёлкнули. Это эффективно и автоматически работает для элементов, добавленных позже.

const list = document.getElementById("menu");

list.addEventListener("click", function (event) {
  // Did the click originate on an <li>?
  const item = event.target.closest("li");
  if (!item || !list.contains(item)) return;

  console.log("You clicked:", item.textContent);
});

С этим единственным обработчиком охватываются все текущие и будущие элементы <li> внутри #menu — не нужно привязывать (или перепривязывать) обработчики к отдельным элементам. Смотрите также Обработка событий в DOM и Отправка пользовательских событий для ознакомления со связанными техниками.

Заключение

Распространение события движется вниз (перехват) и затем вверх (всплытие) через DOM. По умолчанию обработчики выполняются в фазе всплытия; передайте true (или { capture: true }), чтобы слушать в фазе перехвата. Используйте event.target для определения элемента, на котором возникло событие, event.currentTarget — для элемента, обрабатывающего его, а stopPropagation() / stopImmediatePropagation() — для ограничения дальности распространения. Освоив всё это, вы сможете создавать чистые и эффективные взаимодействия — в первую очередь с помощью делегирования событий, — не размещая обработчики по всей странице.

Практика

Практика
Какое свойство указывает на элемент, где событие первоначально возникло, вне зависимости от того, какой обработчик выполняется?
Какое свойство указывает на элемент, где событие первоначально возникло, вне зависимости от того, какой обработчик выполняется?
Практика
По умолчанию в какой фазе выполняются обработчики, добавленные с помощью addEventListener?
По умолчанию в какой фазе выполняются обработчики, добавленные с помощью addEventListener?
Was this page helpful?