События указателя и мыши в JavaScript

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

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

Указатель и мышь - не одно и то же

События указателя начинаются с pointer: pointerdown, pointermove, pointerup и другие. Они работают с мышью, касанием и стилусом через один набор API.

События мыши - mousedown, mousemove, mouseup, mouseenter - по-прежнему встречаются в существующем коде. Их стоит знать, но для нового взаимодействия, которое должно одинаково работать на компьютере и телефоне, обычно удобнее события указателя.

Какое событие выбрать

ЗадачаПодходящее событие
Обычное действие кнопки или ссылкиclick
Начало, движение и завершение пользовательского жестаpointerdown, pointermove, pointerup
Наведение на компонентpointerenter и pointerleave
Поведение, связанное именно с мышью или старым кодомmouse*-события

Для обычной кнопки выбирайте click, а не pointerdown. click означает активацию элемента: оно сработает не только при щелчке мышью, но и при касании, а у нативной кнопки - при активации с клавиатуры.

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

Не назначайте один и тот же обработчик одновременно на pointerdown и mousedown. При работе мышью браузер вызовет оба события, и код может выполниться дважды.

Нажатие, движение и отпускание указателя

pointerdown происходит в момент нажатия, pointermove - при изменении положения указателя, а pointerup - при отпускании. Их мышиные аналоги называются mousedown, mousemove и mouseup.

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

Если логика начинается на pointerdown, учитывайте и pointercancel. Браузер может отменить последовательность событий, например когда распознаёт прокрутку страницы вместо пользовательского жеста.

Чтобы отследить вход указателя на компонент и выход за его пределы, используйте pointerenter и pointerleave. Для событий мыши существуют пары mouseenter и mouseleave.

<article class="product-card">
  <h3>Курс по JavaScript</h3>
  <button type="button">Подробнее</button>
</article>
.product-card {
  padding: 20px;
  border: 1px solid #d1d5db;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.product-card.is-hovered {
  border-color: #2f855a;
  box-shadow: 0 6px 18px rgb(47 133 90 / 20%);
}
const productCard = document.querySelector('.product-card');

productCard.addEventListener('pointerenter', function (event) {
  event.currentTarget.classList.add('is-hovered');
});

productCard.addEventListener('pointerleave', function (event) {
  event.currentTarget.classList.remove('is-hovered');
});

Класс добавляется при входе указателя на карточку и удаляется при выходе. Переход указателя с заголовка на кнопку внутри карточки не вызовет повторное срабатывание этих обработчиков.

pointerover и pointerout, как и mouseover и mouseout, реагируют на переходы между вложенными элементами и всплывают. Они нужны, когда такое поведение действительно требуется. Для обычного наведения на целый компонент выбирайте pointerenter и pointerleave.

Если нужен только визуальный эффект наведения, обычно достаточно CSS-псевдокласса :hover. Не делайте важное действие доступным только при наведении: на сенсорных устройствах постоянного состояния наведения нет.

Координаты и тип устройства

<div class="pointer-area">
  Проведите указателем по этой области
  <output class="coordinates">Координаты появятся здесь</output>
</div>
.pointer-area {
  min-height: 160px;
  padding: 20px;
  border: 1px dashed #2f855a;
  border-radius: 8px;
}

.coordinates {
  display: block;
  margin-top: 12px;
  font-family: monospace;
}
const pointerArea = document.querySelector('.pointer-area');
const coordinates = document.querySelector('.coordinates');

pointerArea.addEventListener('pointermove', function (event) {
  const rect = event.currentTarget.getBoundingClientRect();
  const x = Math.round(event.clientX - rect.left);
  const y = Math.round(event.clientY - rect.top);

  coordinates.textContent = 'Тип: ' + event.pointerType + ', X: ' + x + ', Y: ' + y;
});

clientX и clientY содержат координаты внутри видимой области окна браузера. В примере мы вычитаем положение блока, поэтому получаем координаты внутри самой области.

pageX и pageY считают координаты относительно всего документа с учётом прокрутки. Свойство pointerType обычно содержит ‘mouse’, ‘touch’ или ‘pen’.

pointermove может срабатывать много раз за секунду. В таком обработчике оставляйте только лёгкие действия: обновление координат, положения элемента или состояния интерфейса. Тяжёлые вычисления и запросы к серверу лучше выполнять отдельно.

Итого

Для обычной активации кнопки используйте click. Когда нужно отслеживать нажатие, движение или отпускание указателя на разных устройствах, выбирайте pointerdown, pointermove и pointerup. События мыши остаются полезными для мышеспецифичного поведения и при работе со старым кодом.