Angular Front-end Инженер

Angular Front-end Инженер

Роадмап навыков для прокачки

Базовые типы DOM-событий

Browser APIОбъекты документа и страницыСобытия

Основная идея

DOM-события — это сигналы, которые браузер отправляет при взаимодействии пользователя со страницей или при изменении её состояния. События позволяют JavaScript реагировать на клики, нажатия клавиш, загрузку страницы и многое другое.

Ключевые аспекты

  • События мышиclick, dblclick, mouseenter, mouseleave, mousemove, mousedown, mouseup
  • События клавиатурыkeydown, keyup, keypress (устарел)
  • События формsubmit, change, input, focus, blur
  • События документа/окнаload, DOMContentLoaded, resize, scroll
  • События касания (touch)touchstart, touchmove, touchend

Категории событий

  • Пользовательские действия — инициируются пользователем (клик, ввод)
  • Системные события — генерируются браузером (загрузка, ошибки)
  • Кастомные события — создаются программно через CustomEvent

Плюсы событийной модели

  • Декларативная реакция на действия пользователя
  • Отделение логики от представления
  • Возможность множественных обработчиков на одно событие
  • Всплытие событий позволяет делегировать обработку

Частые ошибки на собеседованиях

  • Путают keydown и keypresskeypress устарел и не срабатывает на служебных клавишах
  • Не знают разницу между mouseenter/mouseleave и mouseover/mouseout (вторые всплывают)
  • Путают load и DOMContentLoadedDOMContentLoaded срабатывает раньше, до загрузки картинок и стилей
  • Забывают про input vs changeinput срабатывает при каждом изменении, change — при потере фокуса

Введение и проблематика

Веб-страницы интерактивны — пользователи кликают по кнопкам, вводят текст, скроллят страницу. Браузер должен каким-то образом сообщать JavaScript о всех этих действиях. Для этого существует событийная модель DOM.

События — это сигналы от браузера о том, что что-то произошло. JavaScript может "подписаться" на эти сигналы и выполнить код в ответ.

Зачем нужны события?

Без событий веб-страницы были бы статичными. События позволяют:

  • Реагировать на клики по кнопкам
  • Валидировать формы при отправке
  • Создавать drag-and-drop интерфейсы
  • Реализовывать горячие клавиши
  • Делать infinite scroll
  • И многое другое

Базовая теория

Что такое DOM-событие

Событие (Event) — это объект, который создаётся браузером при определённых действиях и содержит информацию о том, что произошло:

js
element.addEventListener('click', function(event) {
  console.log(event.type);    // "click"
  console.log(event.target);  // элемент, на котором произошёл клик
  console.log(event.clientX); // координата X курсора
  console.log(event.clientY); // координата Y курсора
});

Классификация событий

События можно разделить на несколько категорий:

graph TB A[DOM Events] --> B[Mouse Events] A --> C[Keyboard Events] A --> D[Form Events] A --> E[Document Events] A --> F[Touch Events] A --> G[Other Events] B --> B1[click, dblclick] B --> B2[mouseenter, mouseleave] B --> B3[mousemove, mousedown, mouseup] C --> C1[keydown, keyup] D --> D1[submit, reset] D --> D2[focus, blur] D --> D3[input, change] E --> E1[DOMContentLoaded] E --> E2[load, unload] F --> F1[touchstart, touchmove, touchend]

События мыши

Базовые события клика

СобытиеКогда срабатывает
clickПри клике (нажатие + отпускание)
dblclickПри двойном клике
mousedownПри нажатии кнопки мыши
mouseupПри отпускании кнопки мыши
contextmenuПри клике правой кнопкой (контекстное меню)
js
const button = document.querySelector('button');
 
button.addEventListener('click', () => {
  console.log('Клик!');
});
 
button.addEventListener('dblclick', () => {
  console.log('Двойной клик!');
});
 
// Порядок событий при клике: mousedown → mouseup → click
button.addEventListener('mousedown', () => console.log('1. mousedown'));
button.addEventListener('mouseup', () => console.log('2. mouseup'));
button.addEventListener('click', () => console.log('3. click'));

События наведения

js
// НЕ всплывают, срабатывают только на целевом элементе
const card = document.querySelector('.card');
 
card.addEventListener('mouseenter', () => {
  card.classList.add('hovered');
  console.log('Курсор вошёл в card');
});
 
card.addEventListener('mouseleave', () => {
  card.classList.remove('hovered');
  console.log('Курсор покинул card');
});
⚠️

Используйте mouseenter/mouseleave когда вам нужно отслеживать вход/выход только из самого элемента. mouseover/mouseout полезны для делегирования событий.

Событие движения мыши

js
document.addEventListener('mousemove', (e) => {
  // Координаты относительно viewport
  console.log(`Viewport: ${e.clientX}, ${e.clientY}`);
 
  // Координаты относительно документа (с учётом скролла)
  console.log(`Document: ${e.pageX}, ${e.pageY}`);
 
  // Координаты относительно экрана
  console.log(`Screen: ${e.screenX}, ${e.screenY}`);
});

События клавиатуры

Основные события

СобытиеКогда срабатываетОсобенности
keydownПри нажатии клавишиСрабатывает для всех клавиш
keyupПри отпускании клавишиСрабатывает для всех клавиш
keypressПри нажатии символьной клавишиУстарел! Не использовать
js
document.addEventListener('keydown', (e) => {
  console.log('Клавиша:', e.key);     // "a", "Enter", "Escape"
  console.log('Код:', e.code);        // "KeyA", "Enter", "Escape"
  console.log('Повтор:', e.repeat);   // true если клавиша зажата
 
  // Модификаторы
  console.log('Ctrl:', e.ctrlKey);
  console.log('Shift:', e.shiftKey);
  console.log('Alt:', e.altKey);
  console.log('Meta (Cmd/Win):', e.metaKey);
});

e.key возвращает символ с учётом раскладки ("a", "ф"). e.code возвращает физическую клавишу ("KeyA" независимо от раскладки).

Пример: горячие клавиши

js
document.addEventListener('keydown', (e) => {
  // Ctrl+S (Cmd+S на Mac)
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault(); // отменяем стандартное сохранение
    saveDocument();
    console.log('Документ сохранён');
  }
 
  // Escape для закрытия модального окна
  if (e.key === 'Escape') {
    closeModal();
  }
});

События форм

События фокуса

js
const input = document.querySelector('input');
 
// Получение фокуса
input.addEventListener('focus', () => {
  console.log('Поле получило фокус');
  input.parentElement.classList.add('focused');
});
 
// Потеря фокуса
input.addEventListener('blur', () => {
  console.log('Поле потеряло фокус');
  input.parentElement.classList.remove('focused');
  validateField(input);
});
 
// focusin/focusout — всплывают (для делегирования)
document.addEventListener('focusin', (e) => {
  console.log('Фокус на:', e.target);
});

События изменения

js
// input — срабатывает при КАЖДОМ изменении
const searchInput = document.querySelector('#search');
 
searchInput.addEventListener('input', (e) => {
  const query = e.target.value;
  console.log('Текущее значение:', query);
 
  // Идеально для живого поиска
  performSearch(query);
});

Событие отправки формы

js
const form = document.querySelector('form');
 
form.addEventListener('submit', (e) => {
  e.preventDefault(); // отменяем перезагрузку страницы
 
  const formData = new FormData(form);
 
  // Получение данных
  const name = formData.get('name');
  const email = formData.get('email');
 
  // Валидация и отправка
  if (validateForm(formData)) {
    sendData(formData);
  }
});

События документа и окна

Загрузка страницы

js
// DOM готов (HTML распарсен), но картинки/стили могут ещё загружаться
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM готов');
  initializeApp();
});
 
// Вся страница загружена (включая картинки, стили, iframe)
window.addEventListener('load', () => {
  console.log('Страница полностью загружена');
  hideLoader();
});
 
// Пользователь покидает страницу
window.addEventListener('beforeunload', (e) => {
  if (hasUnsavedChanges) {
    e.preventDefault();
    e.returnValue = ''; // Показать диалог подтверждения
  }
});
⚠️

Всегда используйте DOMContentLoaded вместо load если вам нужно только работать с DOM. load ждёт загрузки всех ресурсов, что может быть долго.

Скролл и ресайз

js
// Скролл
window.addEventListener('scroll', () => {
  const scrollY = window.scrollY;
 
  // Показать кнопку "Наверх" при скролле вниз
  if (scrollY > 500) {
    showBackToTopButton();
  }
});
 
// Изменение размера окна
window.addEventListener('resize', () => {
  const width = window.innerWidth;
  const height = window.innerHeight;
 
  console.log(`Размер окна: ${width}x${height}`);
});

События scroll и resize могут срабатывать очень часто. Используйте throttle или debounce для оптимизации производительности.


События касания (Touch)

js
const slider = document.querySelector('.slider');
let startX = 0;
 
slider.addEventListener('touchstart', (e) => {
  startX = e.touches[0].clientX;
  console.log('Касание началось');
});
 
slider.addEventListener('touchmove', (e) => {
  const currentX = e.touches[0].clientX;
  const diff = currentX - startX;
  console.log('Смещение:', diff);
});
 
slider.addEventListener('touchend', () => {
  console.log('Касание завершено');
});

Плюсы и минусы

АспектПлюсыМинусы
ГибкостьМножество типов событийНужно знать различия между похожими
ПроизводительностьЭффективная обработкаЧастые события требуют оптимизации
КроссбраузерностьСтандартизированы W3CМелкие различия в браузерах

Вопросы интервьюера

Q: Чем отличается mouseenter от mouseover?

mouseenter не всплывает и срабатывает только при входе в сам элемент. mouseover всплывает и срабатывает также при входе в дочерние элементы.

Q: Когда использовать input, а когда change?

input — для живой реакции на каждое изменение (живой поиск, подсчёт символов). change — для реакции на финальное значение (валидация после ввода).

Q: Чем DOMContentLoaded отличается от load?

DOMContentLoaded срабатывает когда HTML распарсен и DOM построен. load ждёт загрузки всех ресурсов (картинки, стили, шрифты).

Q: Почему keypress устарел?

keypress не срабатывает для служебных клавиш (Escape, стрелки, F1-F12). Используйте keydown для всех случаев.


Источники