События и addEventListener()

Содержание

Метод addEventListener() назначает обработчик события: функцию, которая выполнится, когда с указанным объектом произойдёт нужное действие. События поддерживают Document, DOM-элементы, Window и другие объекты.

addEventListener() - один из трёх способов назначить обработчик события наряду с HTML-атрибутом и присваиванием функции свойству объекта.

<!-- добавление атрибута -->
<button onclick="alert('Терпение и труд всё перетрут')">
  Хочу стать программистом!
</button>

<script>
const myFirstEvent = document.querySelector('button');

// обращаемся к свойству напрямую
myFirstEvent.onclick = function () {
  alert('Да будет так!');
};

// используем метод addEventListener
myFirstEvent.addEventListener('click', function () {
  alert('Дорогу осилит идущий');
});
</script>

addEventListener() имеет важное преимущество перед остальными способами: метод позволяет назначить несколько обработчиков одного события. У объекта только одно свойство, например onclick для клика или onmousemove для движения мыши, поэтому повторное присваивание функции этому свойству перезапишет предыдущий обработчик.

В примере выше это видно на практике: модальное окно с Да будет так! появится, а Терпение и труд всё перетрут - нет.

Некоторые события можно отследить только через addEventListener(): например, для DOMContentLoaded нет свойства-обработчика вроде onDOMContentLoaded.

Синтаксис

element.addEventListener(eventType, handler, options);

element - объект, действие по которому отслеживаем.

eventType - тип события, которое мы хотим отследить: клик, прокрутка мыши, нажатие клавиши и т.д. Написание чувствительно к регистру: click правильно, Click или CLICK неправильно.

handler - имя функции или сама функция, которая будет выполнена после наступления события.

options - необязательный объект. В нём можно указать, например, следующие параметры:

  • capture: значение true или false, которое задаёт фазу обработки. По умолчанию false: на предках обработчик сработает при всплытии; true - при перехвате, до достижения цели. Если передать третьим аргументом булево значение, оно задаст только этот параметр.
  • once: значение типа boolean; если указать true, обработчик будет удалён после первого вызова.
  • passive: сообщает браузеру, что обработчик не будет отменять стандартное действие. В пассивном обработчике вызов preventDefault() игнорируется и может вызвать предупреждение в консоли.
<button>В скором времени я стану JavaScript middle</button>

<script>
const clickButton = document.querySelector('button');

clickButton.addEventListener('click', showMessage, {
  capture: false,
  once: true,
  passive: false,
});

function showMessage() {
  alert('У тебя всё получится, я в тебя верю!');
}
</script>

При клике на кнопку появится модальное окно с сообщением. При втором клике ничего не произойдёт, потому что once равно true: после первого вызова обработчик удаляется.

removeEventListener()

Удалить обработчик события можно методом removeEventListener() в нужный момент. Чтобы это сработало, нужно передать тот же тип события, ту же ссылку на функцию и такое же значение capture. Анонимную функцию можно удалить, только если заранее сохранить ссылку на неё: заново написанная, даже идентичная функция будет другим объектом.

<button>Удалить обработчик события</button>

<script>
const clickButton = document.querySelector('button');

clickButton.addEventListener('click', showMessage);

function showMessage() {
  if (clickButton.innerText === 'Удалить обработчик события') {
    alert('Обработчик удален');
    clickButton.removeEventListener('click', showMessage);
  } else {
    alert('Обработчик не удален');
  }
}
</script>

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

Популярные события

Рассмотрим события, которые отслеживаются чаще других:

click - активация элемента: обычно кликом или касанием, а для некоторых элементов - и с клавиатуры;

contextmenu - попытка открыть контекстное меню, обычно правой кнопкой мыши;

mouseover / mouseout - наведение на элемент курсора мыши / курсор покидает элемент;

mousemove - движение мыши;

keydown / keyup - клавиша нажата / клавиша отпущена;

DOMContentLoaded - HTML разобран и DOM-дерево построено; изображения и другие ресурсы страницы могут ещё загружаться.

В инструментах разработчика можно посмотреть свойства-обработчики DOM-элемента: выберите элемент, откройте Свойства (Properties) и отфильтруйте их по on. Это не полный список событий, а только доступные свойства-обработчики.

свойства элемента в браузере

Как добавить обработчик на множество элементов

addEventListener и forEach

<button class="myChoice">JavaScript</button>
<button class="myChoice">Python</button>

<script>
const choice = document.querySelectorAll('.myChoice');

choice.forEach((choiceItem) => {
  choiceItem.addEventListener('click', () => answer(choiceItem.innerHTML));
});

function answer(text) {
  if (text === 'JavaScript') {
    alert('Отличный выбор!');
  } else {
    alert('Возможно вы ошиблись?');
  }
}
</script>

В этом примере мы получили все элементы с классом myChoice через querySelectorAll(), а затем с помощью forEach() назначили обработчик каждой кнопке.

Делегирование событий

Другой вариант - делегирование событий. Оно полезно, когда у однотипных или динамически добавляемых элементов есть общий родитель.

<style>
td {
  width: 90px;
  height: 90px;
  border: 1px solid black;
  text-align: center;
}

.hideText {
  color: white;
}
</style>

<table>
  <tr>
    <td class="hideText">JavaScript</td>
    <td class="hideText">React</td>
    <td class="hideText">HTML</td>
  </tr>
</table>

<script>
const getTable = document.querySelector('table');

getTable.addEventListener('click', function (event) {
  const cell = event.target.closest('.hideText');

  if (cell && getTable.contains(cell)) {
    cell.classList.remove('hideText');
  }
});
</script>

В этом примере обработчик назначен общему родителю - таблице. С помощью event.target.closest(‘.hideText’) находим ячейку, по которой кликнули, и удаляем у неё класс, делающий текст белым.

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

Всплытие - этап, на котором событие после целевого элемента проходит к его родителям. Если событие всплывает и обработчики добавлены без capture, сначала сработает обработчик на целевом элементе, затем - на его предках.

<body> - в четвертую очередь
  <span></span> - не сработает
  <span> - в третью очередь
    <span> - во вторую очередь
      <span></span> - кликаем сюда, обработчик сработает на этом элементе в первую очередь
    </span>
  </span>
</body>

Перехват (capture) - этап, на котором событие идёт от внешних предков к целевому элементу. Обработчик на предке с capture: true сработает до обработчика на цели. Затем, если событие всплывает, оно вернётся от цели к предкам.

<body> - в пятую очередь
  <span></span> - не сработает
  <span> - в четвертую очередь
    <span> - элемент со свойством capture: true, в первую очередь
      <span> - в третью очередь
        <span></span> - кликаем сюда, обработчик сработает на этом элементе во вторую очередь
      </span>
    </span>
  </span>
</body>

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

Пример с квадратами
<style>
.square1 {
  display: flex;
  width: 600px;
  height: 600px;
  align-items: center;
  justify-content: center;
  background: red;
}

.square2 {
  display: flex;
  width: 450px;
  height: 450px;
  align-items: center;
  justify-content: center;
  background: blue;
}

.square3 {
  display: flex;
  width: 350px;
  height: 350px;
  align-items: center;
  justify-content: center;
  background: yellow;
}

.square4 {
  width: 250px;
  height: 250px;
  background: white;
}
</style>

<div class="square1">
  <div class="square2">
    <div class="square3">
      <div class="square4"></div>
    </div>
  </div>
</div>

<script>
const squareFirst = document.querySelector('.square1');
const squareSecond = document.querySelector('.square2');
const squareThird = document.querySelector('.square3');
const squareFourth = document.querySelector('.square4');

squareFirst.addEventListener('click', () => clickSquare('Квадрат №1'));
squareSecond.addEventListener('click', () => clickSquare('Квадрат №2'));
squareThird.addEventListener('click', () => clickSquare('Квадрат №3'), {
  capture: true,
});
squareFourth.addEventListener('click', () => clickSquare('Квадрат №4'));

function clickSquare(text) {
  console.log(text);
}
</script>

Получаем доступ к элементу через this

В обычной функции-обработчике, добавленной через addEventListener(), this указывает на элемент, для которого сейчас выполняется обработчик. Это то же значение, что и event.currentTarget. У стрелочной функции нет собственного this, поэтому в ней лучше использовать event.currentTarget.

<button class="btn">После того, как выучу JS, примусь за React</button>

<script>
const getButton = document.querySelector('.btn');

getButton.addEventListener('click', printBtnText);

function printBtnText() {
  alert(this.innerHTML);
}
</script>

Итого

Обработчик события позволяет запускать код в ответ на действия пользователя и другие события браузера. Метод addEventListener() - основной способ назначать такие обработчики объектам.

Обработчик можно удалить автоматически с помощью once: true или вручную через removeEventListener(), передав ту же функцию.

Чтобы обрабатывать действия множества элементов, можно назначить слушатель каждому из них через forEach() или использовать делегирование: назначить обработчик общему предку и определить целевой элемент через event.target.