События мыши: click, mouseover, mouseenter

События мыши позволяют реагировать на взаимодействие пользователя с указателем: клики, наведение, движение, нажатие и отпускание кнопок мыши; каждое событие несёт координаты, информацию о кнопке и модификаторах.

Зачем нужно

Большинство пользовательских взаимодействий в браузере — события мыши. Понимание разницы между всплывающими и невсплывающими событиями (mouseover vs mouseenter) позволяет выбрать правильный подход и не создавать лишних обработчиков.

Где используется

  • Интерактивные кнопки, меню, карточки
  • Drag and drop
  • Кастомные контекстные меню
  • Tooltip и popup при наведении
  • Canvas рисование

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

Событие Когда Всплывает
click Нажата и отпущена левая кнопка Да
dblclick Двойной клик Да
mousedown Кнопка нажата Да
mouseup Кнопка отпущена Да
mousemove Курсор движется Да
mouseover Вход курсора (в том числе в потомков) Да
mouseout Выход курсора Да
mouseenter Вход курсора в элемент Нет
mouseleave Выход курсора из элемента Нет
contextmenu Правый клик Да

click и свойства события

const button = document.querySelector('#btn');

button.addEventListener('click', (event) => {
  // Координаты клика
  console.log('Относительно viewport:', event.clientX, event.clientY);
  console.log('Относительно страницы:', event.pageX, event.pageY);
  console.log('Относительно элемента:', event.offsetX, event.offsetY);

  // Кнопка мыши: 0=левая, 1=средняя, 2=правая
  console.log('Кнопка:', event.button);

  // Модификаторы
  console.log('Ctrl:', event.ctrlKey, 'Shift:', event.shiftKey);

  // Цель события
  console.log('Элемент:', event.target);
  console.log('Текущий обработчик:', event.currentTarget);
});

mouseover vs mouseenter (ключевое отличие)

<div class="parent">
  <span class="child">Дочерний</span>
</div>
const parent = document.querySelector('.parent');

// mouseover — срабатывает при входе в parent И при входе в child (всплывает)
parent.addEventListener('mouseover', () => {
  console.log('mouseover'); // сработает 2 раза: при входе в parent и в child
});

// mouseenter — срабатывает ТОЛЬКО при первом входе в parent (не всплывает)
parent.addEventListener('mouseenter', () => {
  console.log('mouseenter'); // сработает 1 раз — при входе в parent
});

// Правило: для hover-эффектов на элемент с потомками используй mouseenter/mouseleave

Tooltip при наведении

const card = document.querySelector('.product-card');
const tooltip = document.querySelector('.tooltip');

card.addEventListener('mouseenter', () => {
  tooltip.style.display = 'block';
});

card.addEventListener('mouseleave', () => {
  tooltip.style.display = 'none';
});

// Позиционирование тултипа по курсору
card.addEventListener('mousemove', (e) => {
  tooltip.style.left = `${e.pageX + 10}px`;
  tooltip.style.top  = `${e.pageY + 10}px`;
});

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

// Вместо обработчика на каждом элементе — один на родителе
const list = document.querySelector('.item-list');

list.addEventListener('click', (event) => {
  // Находим ближайшего предка с классом .item
  const item = event.target.closest('.item');
  if (!item) return;

  const id = item.dataset.id;
  console.log('Клик по item:', id);
});

Правый клик и contextmenu

document.addEventListener('contextmenu', (event) => {
  event.preventDefault(); // отменяем стандартное меню браузера
  showCustomMenu(event.clientX, event.clientY);
});

document.addEventListener('click', () => {
  hideCustomMenu; // закрываем при клике вне меню
});

Частые ошибки

1. Путаница mouseover / mouseenter

// Если parent большой, а внутри много потомков — mouseover будет срабатывать
// при каждом пересечении границ дочерних элементов
// Используй mouseenter/mouseleave для hover-состояний

2. Обработчик на каждом элементе списка

// Плохо: 1000 обработчиков для 1000 элементов
items.forEach(item => item.addEventListener('click', handler));

// Хорошо: 1 обработчик + делегирование
container.addEventListener('click', (e) => {
  const item = e.target.closest('[data-id]');
  if (item) handler(item.dataset.id);
});

3. Использование onclick (атрибут HTML)

// Плохо: только один обработчик, смешение разметки и логики
// <button onclick="doSomething">...</button>

// Хорошо: addEventListener — несколько обработчиков, разделение
button.addEventListener('click', doSomething);

Связанные темы

Ресурсы