Такие события, как mousedown, mousemove, mouseup существовали в JavaScript уже давно для отслеживания действий мыши и, в том числе, заменяли собой события перетаскивания. Но с появлением тач-устройств появилась и необходимость в специальных событиях, которые реагируют не только на мышь, но и на стилус или палец.
События pointerdown, pointermove, pointerup и т.д. — это часть Pointer Events API, которая объединяет в себе обработку:
- мыши (
mousedown, mousemove, mouseup) - касаний (тач-события:
touchstart, touchmove, touchend) - стилуса (например, Surface Pen или Apple Pencil)
Их цель — унифицировать взаимодействие с указательными устройствами.
Давайте рассмотрим их поподробней.
Событие pointerdown
Событие pointerdown срабатывает, когда пользователь нажимает на элемент (любой указатель: мышь, палец, стилус).
Аналогичные события, которые оно заменяет:
| Устройство | Старое событие | Новое (универсальное) |
|---|---|---|
| Мышь | mousedown | pointerdown |
| Палец (тачскрин) | touchstart | pointerdown |
| Стилус | — | pointerdown |
Элементарная проверка:
| 1 2 3 | element.addEventListener('pointerdown', (event) => { output.textContent = 'Нажатие указателя:'+ event.pointerType; }); |
Событие pointerup
Событие pointerup срабатывает, когда пользователь отпускает указатель, будь то мышь, палец или стилус. Аналоги: mouseup (для мыши) и touchend (для касания). Часто используется, чтобы закончить действие: например, остановить рисование, завершить перетаскивание, зафиксировать выбор и т.п.
В примере ниже мы будем рисовать кружок на экране в момент отпускания указателя (pointerup) и при этом показывать, какой тип устройства использовался (event.pointerType).
See the Pen pointerup при рисовании by HTML-plus (@ambassador) on CodePen.
Еще один пример с отображением результата выбора с помощью обработки события pointerup.
See the Pen Выбор с помощью pointerup by HTML-plus (@ambassador) on CodePen.
Событие pointermove
Событие pointermove срабатывает, когда указатель двигается по экрану (мышь перемещается, палец скользит по экрану, стилус движется).
Пример с отображением координат
| 1 2 3 | box.addEventListener('pointermove', (event) => { output.innerText = `Движение указателя мыши: X: ${event.clientX}, Y: ${event.clientY}`; }); |
Пробуем:
Аналогичные события:
| Устройство | Старое событие | Новое (универсальное) |
|---|---|---|
| Мышь | mousemove | pointermove |
| Палец | touchmove | pointermove |
| Стилус | — | pointermove |
Примеры с использованием pointermove
В примере при перемещении курсора мы перемещаем элемент с градиентом, создавая эффект свечения.
See the Pen Neon lights by Andrii Rodzyk (@rodzyk) on CodePen.
И еще 3 примера с масками:
1. С линейным градиентом
See the Pen Tilt-Shift Blur Effect by Wakana Y.K. (@wakana-k) on CodePen.
2. С радиальным градиентом:
See the Pen Focus by Wakana Y.K. (@wakana-k) on CodePen.
3. Более сложная маска для нескольких карточек с кнопками
See the Pen glowy hover effect by Ines (@inescodes) on CodePen.
Преимущества Pointer Events
- Унификация: один обработчик на все типы устройств.
- Удобная идентификация указателя: через
event.pointerType("mouse", "touch", "pen"). - Поддержка нескольких указателей: например, несколько пальцев (
multitouch), каждый с уникальнымpointerId. - Работает без необходимости обрабатывать
touchиmouseотдельно.
Сравнение с Touch и Mouse Events
| Mouse Events | Touch Events | Pointer Events | |
|---|---|---|---|
| Универсальность | ❌ Только мышь | ❌ Только касания | ✅ Всё вместе |
| Поддержка стилуса | ❌ | ❌ | ✅ |
| Multitouch поддержка | ❌ | ✅ | ✅ |
| Легкость в работе | ✅ | ❌ | ✅ |
Полезные свойства PointerEvent
pointerId: уникальный ID указателя (для multi-touch)pointerType:"mouse", "touch", "pen"pressure: сила нажатия (для стилуса)isPrimary: основной ли это указатель (например, первый палец)
В примере ниже вы можете протестировать свойство pressure. Это будет работать на стилусе (например, Apple Pencil, Surface Pen) и на некоторых touch-дисплеях с поддержкой давления. Мышью вы будете создавать одинаковые линии.
See the Pen event.pressure by HTML-plus (@ambassador) on CodePen.
Что делает код:
| Свойство | Что делает |
|---|---|
e.pressure | Возвращает значение от 0.0 до 1.0 |
lineWidth | Изменяется динамически в зависимости от давления |
touch-action: none | Отключает скролл/зум для тач-устройств на <canvas> |
Примечания:
- Мышь чаще всего даёт
pressure = 0.5, без динамики. - Стилус может давать значения от
0.0(не нажато) до1.0(максимум). - На iPad с Pencil — работает прекрасно.
- На обычных телефонах часто не работает (нет датчика давления).
Пример с перетаскиванием элемента
Используем события для перетаскивания элемента с абсолютным позиционированием.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <div class="test" style="position: relative; height: 500px; overflow: hidden;"> <div id="dragbox" style=" width: 120px; height: 100px; padding-top: 30px; text-align: center; background: #36b8f4; position: absolute; top: 100px; left: 100px; touch-action: none; /* ВАЖНО */"> Тяни меня </div> </div> <script> const dragbox = document.getElementById('dragbox'); let offsetX = 0; let offsetY = 0; let isDragging = false; dragbox.addEventListener('pointerdown', (event) => { isDragging = true; offsetX = event.clientX - dragbox.offsetLeft; offsetY = event.clientY - dragbox.offsetTop; // Захватываем указатель, чтобы получать события даже вне элемента dragbox.setPointerCapture(event.pointerId); }); dragbox.addEventListener('pointermove', (event) => { if (!isDragging) return; const x = event.clientX - offsetX; const y = event.clientY - offsetY; dragbox.style.left = `${x}px`; dragbox.style.top = `${y}px`; }); dragbox.addEventListener('pointerup', (event) => { isDragging = false; dragbox.releasePointerCapture(event.pointerId); }); </script> |
Пример в действии:
На что стоит обратить внимание:
touch-action: noneв CSS - чтобы перетаскивание пальцем работало, нужно отключить поведение по умолчанию браузера (скролл, зум и т.д.). Иначе браузер будет "перехватывать" жесты, иpointermoveне будет срабатывать корректно.- метод
setPointerCapture()- захватывает все pointer-события, даже если палец/курсор уходит за границы элемента — очень важно для drag'а.
Пример: перетаскивание элементов между колонками:
Давайте используем события Pointer Events API для перетаскивания карточек между двумя колонками. В рамках одной колонки мы тоже можем перетаскивать карточки.
See the Pen Draggable cards with pointerevents by HTML-plus (@ambassador) on CodePen.
Что тут происходит:
| Событие | Объяснение |
|---|---|
pointerdown | Карточка становится position: absolute, сохраняем смещение. |
pointermove | Следим за движением и перемещаем карточку, ищем колонку, вставляем placeholder. |
pointerup | Возвращаем карточку в DOM (в место placeholder), убираем абсолютную позицию. |
Создание перетаскиваемого слайдера
Этот пример идеально подходит для сравнения фотографий «до и после» некого изменения. Для этого нам понадобится:
- 2 фоновых изображения (черно-белое и цветное)
- спрятанный ползунок (
<input type="range">) - слушатели событий для ползунка:
pointerdown,pointerup,pointermove.
Для того чтобы нужно узнать положение ползунка по отношению к его родителю в процентах, используем рисунок, поясняющий все составные части:
Таким образом, процент позиции рассчитывается по формуле:
| 1 | const percent = (100 * (clientX - containerX)) / containerWidth; |
Используем процент в функции onMove():
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const onMove = (clientX) => { const { x: containerX, width: containerWidth } = containerElement.getBoundingClientRect(); const left = clientX - containerX; const newPercent = (100 * left) / containerWidth; const minBoundedPercent = Math.max(newPercent, 0); const minAndMaxBoundedPercent = Math.min( minBoundedPercent, 100, ); updatePercent(minAndMaxBoundedPercent); }; |
Еще один нюанс касается взаимодействия пользователя с сенсорным устройством. Для того чтобы оно осуществлялось без задержек, необходимо, чтобы мобильный браузер отменил указатель благодаря CSS-свойству touch-action: none.
| 1 2 3 4 5 6 7 8 9 | #container { max-width: 500px; width: 100%; height: 500px; position: relative; overflow: hidden; margin: 30px auto; touch-action: none; /* ВАЖНО */ } |
Результат можно протестировать ниже.
See the Pen event.pressure in pointermove by HTML-plus (@ambassador) on CodePen.
Кроме перечисленных событий вы можете также обрабатывать события наведения и уведения любых типов указателей.