Последние несколько лет с удовольствием поддерживаю библиотеку для джойстиков в 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/

Спасибо за прочтение!