Урок 05

Всплытие и погружение

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

Фазы

<div class="outer">
    <div class="inner">
        <button>Клик</button>
    </div>
</div>

При клике на кнопку:

  1. Capture — от document вниз к кнопке
  2. Target — сама кнопка
  3. Bubble — от кнопки вверх к document

Всплытие

По умолчанию обработчики срабатывают на фазе всплытия — от цели вверх:

inner.addEventListener('click', () => console.log('inner'));
outer.addEventListener('click', () => console.log('outer'));

// клик по button:
// inner
// outer

Погружение

Опция capture: true:

outer.addEventListener('click', () => console.log('outer capture'), true);
inner.addEventListener('click', () => console.log('inner capture'), true);

// клик по button:
// outer capture
// inner capture

Порядок — сверху вниз.

target и currentTarget

outer.addEventListener('click', (event) => {
    event.target;        // button — где кликнули
    event.currentTarget; // outer — где обработчик
});

target не меняется, currentTarget — элемент с обработчиком.

stopPropagation

Останавливает всплытие:

inner.addEventListener('click', (event) => {
    event.stopPropagation();
});

outer.addEventListener('click', () => {
    console.log('не сработает');
});

stopImmediatePropagation

Останавливает и всплытие, и другие обработчики на том же элементе:

button.addEventListener('click', (event) => {
    event.stopImmediatePropagation();
    console.log('1');
});

button.addEventListener('click', () => {
    console.log('2'); // не сработает
});

preventDefault

Отменяет поведение браузера:

form.addEventListener('submit', (event) => {
    event.preventDefault(); // без перезагрузки
});

link.addEventListener('click', (event) => {
    event.preventDefault(); // без перехода
});

Делегирование

Главный практический приём. Один обработчик на родителе вместо многих на детях:

ul.addEventListener('click', (event) => {
    const li = event.target.closest('li');
    if (!li) return;

    console.log('клик по', li.textContent);
});

Плюсы:

  • Работает для динамически добавленных элементов
  • Меньше обработчиков — меньше памяти
  • Проще код

Пример: таблица

table.addEventListener('click', (event) => {
    const button = event.target.closest('button[data-action]');
    if (!button) return;

    const row = button.closest('tr');
    const id = row.dataset.id;

    if (button.dataset.action === 'delete') {
        deleteRow(id);
    }
});

Пример: модалка

modal.addEventListener('click', (event) => {
    if (event.target === modal) {
        closeModal();
    }
});

Клик по фону (не по содержимому) закрывает.

Отмена в capture

Можно перехватить до того, как обработчики на элементе сработают:

document.addEventListener('click', (event) => {
    if (event.target.closest('.no-click')) {
        event.stopPropagation();
    }
}, true);

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

focus, blur, mouseenter, mouseleave, load — не всплывают.

Аналоги, которые всплывают:

  • focus → focusin
  • blur → focusout
  • mouseenter → mouseover
  • mouseleave → mouseout

Итоги

  • Фазы: capture → target → bubble
  • По умолчанию — всплытие
  • capture: true для погружения
  • stopPropagation останавливает
  • preventDefault отменяет поведение
  • Делегирование — главный приём