Последние несколько лет с удовольствием поддерживаю библиотеку для джойстиков в React — работает отлично, но есть ряд мелких проблем:
- Мы должны обрабатывать события
mouse
иtouch
отдельно — для поддержки функции мультитач нам нужно получить доступ к полю касания, которого нет в событиях мыши. - Если мы начнем перетаскивать джойстик, а курсор покинет область просмотра, мы не сможем отследить угол и скорость курсора.
Итак, давайте рассмотрим их внутри проекта react-joystick-component
:
С точки зрения регистрации слушателя ключевое отличие заключается в том, что нам нужно отслеживать идентификатор касания, чтобы мы могли различать взаимодействия. У нас нет этой проблемы с событиями мыши, поскольку курсор обычно является одноэлементным.
if (e.type === 'mousedown') { window.addEventListener(InteractionEvents.MouseUp, this._boundMouseUp); window.addEventListener(InteractionEvents.MouseMove, this._boundMouseMove); } else { this._touchIdentifier = e.targetTouches[0].identifier; window.addEventListener(InteractionEvents.TouchEnd, this._boundMouseUp); window.addEventListener(InteractionEvents.TouchMove, this._boundMouseMove); }
Как мы можем интегрировать это с API указателя?
Упрощение слушателя
Регистрация прослушивателя событий выше становится ниже
window.addEventListener('pointerup', this._boundMouseUp); window.addEventListener('pointermove', this._boundMouseMove); this._touchIdentifier = e.targetTouches[0].identifier;
Мультитач-события
Учитывая, что наш прослушиватель перемещений находится на window
, нам нужно определить между различными источниками событий.
Текущая реализация
if(event.targetTouches && event.targetTouches[0].identifier !== this._touchIdentifier){ return; }
Я вижу в этом запах кода/загрязненную логику — мы проверяем событие, чтобы увидеть, есть ли в нем данные касания, а затем, если идентификаторы не совпадают, мы спасаемся.
Реализация указателя
Хотя pointerId
немного избыточен для событий мыши, мы удаляем большую часть логики этим изменением.
if(event.pointerId !== this._touchIdentifier) return;
Получение положения указателя
Текущая реализация
Как и везде, у нас был какой-то неуклюжий код для обработки позиционирования как для событий касания, так и для событий мыши — теперь мы можем упростить это.
let absoluteX = null; let absoluteY = null; if (event instanceof MouseEvent) { absoluteX = event.clientX; absoluteY = event.clientY; } else { absoluteX = event.targetTouches[0].clientX; absoluteY = event.targetTouches[0].clientY; }
становится
const absoluteX = event.clientX; const absoluteY = event.clientY;
Обработка сенсорного ввода в мультисенсорных средах
Учитывая, что при срабатывании прослушивателя событий touchend
могло произойти несколько касаний, нам пришлось выполнить некоторую итерацию, чтобы проверить, какое касание отменить.
if(event.touches){ for(const touch of event.touches){ // this touch id is still in the TouchList, so this touchend should be ignored if(touch.identifier === this._touchIdentifier){ return; } } }
Теперь это можно упростить:
if(event.pointerId !== this._touchIdentifier) return;
Упрощенная интеграция с TypeScript
Еще один запах в этой кодовой базе заключается в том, что у нас есть методы, обрабатывающие несколько типов событий, что вызывало проблемы с вводом текста — я не мог заставить это работать с объединением MouseEvent | TouchEvent
, поэтому я прибегнул к any
:
private _mouseDown(e: MouseEvent| any) {
Теперь, когда триггер события стандартизирован, мы можем правильно ввести метод:
private _pointerDown(e: PointerEvent) {
Обработка положения курсора вне области просмотра
Для этого мы можем использовать метод setPointerCapture — он привязывает последующие события указателя к конкретному элементу, который мы перетаскиваем.
Регистрация
window.addEventListener(InteractionEvents.PointerUp, this._boundPounterUp); window.addEventListener(InteractionEvents.PointerMove, this._boundPointerMove); this._pointerId = e.pointerId // We're setting this directly on the React stick ref this._stickRef.current.setPointerCapture(this._pointerId);
Вуаля, проблема решена
Не совсем
Одной из основных целей этого рефакторинга была стандартизация событий мыши и касания, однако, если мы посмотрим на эмулятор касания, мы увидим проблему:
Хм.. ну.. он сломан.
Немного покопавшись, я наткнулся на CSS-селектор touchAction. По сути, происходит то, что PointerEvent
отменяется TouchEvent
. Настройка touch-action:none
останавливает это.
И вот оно, стандартизированные события указателя. Не стесняйтесь взглянуть на коммит — https://github.com/elmarti/react-joystick-component/commit/129fd6dd091788e5e8a16fc342a1cbfbcb55c662. от него.
Раз уж вы здесь, я решил также упомянуть, что нанимаю full stack инженера в свою компанию — Noala — давайте работать вместе! https://www.linkedin.com/jobs/view/2848986961/
Спасибо за прочтение!