Touch события

OpenLayers изначально проектировалась как библиотека, ориентированная на унификацию взаимодействия с картой через абстракцию над различными устройствами ввода. В современной архитектуре взаимодействие с сенсорными устройствами не выделяется в отдельный «touch-only» слой, а интегрировано в единый механизм обработки указателей через Pointer Events, что обеспечивает совместимость с мышью, пером и сенсорными экранами.


Архитектурная модель обработки касаний

В основе обработки касаний в OpenLayers лежит переход от классических DOM-событий touchstart / touchmove / touchend к универсальной модели pointerdown / pointermove / pointerup.

Ключевая идея заключается в следующем:

  • все устройства ввода представлены как pointer
  • тип устройства определяется через pointerType
  • логика взаимодействия с картой не дублируется для разных устройств
  • жесты (pan, zoom, rotate) строятся поверх единого потока координат

Основные преимущества модели Pointer Events

  • унификация обработки мыши и touch
  • снижение сложности внутренних контроллеров
  • корректная работа multi-touch
  • поддержка стилусов и давления (pressure)
  • отсутствие необходимости вручную синхронизировать touch-события

Слой взаимодействия и Map Browser Events

Карта в OpenLayers генерирует события через систему MapBrowserEvent. Это абстракция над нативными DOM-событиями.

Каждое взаимодействие пользователя с картой проходит цепочку:

  1. DOM событие (pointerdown, pointermove)
  2. преобразование координат в pixel и coordinate
  3. создание MapBrowserEvent
  4. передача в обработчики карты и взаимодействий (interaction)

Структура MapBrowserEvent

Ключевые поля:

  • type — тип события
  • coordinate — географические координаты
  • pixel — пиксельные координаты на viewport
  • originalEvent — исходное DOM событие
  • map — ссылка на объект карты

Touch как часть Pointer Interaction System

OpenLayers не использует отдельный класс touch-интеракций. Вместо этого используется система Interaction, где жесты реализуются через анализ pointer-потока.

Основные жесты:

  • перемещение карты (drag / pan)
  • масштабирование (pinch zoom)
  • вращение (rotation gesture)
  • инерционное движение (kinetic scrolling)

Каждый жест определяется количеством активных pointer-ов:

  • 1 pointer → pan
  • 2 pointers → pinch zoom + rotation
  • 2 pointers → игнорирование или кастомная логика


Панорамирование (Pan) через touch

При одном активном касании происходит обработка смещения карты.

Логика основана на разнице координат между событиями pointerdown и pointermove.

Механика:

  • фиксируется стартовая точка касания
  • при движении вычисляется delta
  • карта смещается на противоположное значение delta

Внутри используется преобразование:

  • экранные пиксели → географические координаты
  • с учётом текущего view resolution

Pinch Zoom: масштабирование жестом двумя пальцами

Pinch-zoom реализуется через анализ расстояния между двумя pointer-точками.

Основная идея:

  • вычисляется расстояние между двумя касаниями
  • сравнивается с предыдущим состоянием
  • изменение расстояния преобразуется в коэффициент масштаба

Формально:

  • d0 — начальное расстояние
  • d1 — текущее расстояние
  • коэффициент масштабирования = d1 / d0

Далее этот коэффициент применяется к View:

  • изменение resolution
  • центр масштабирования вычисляется как midpoint между двумя точками касания

Вращение карты (Rotation gesture)

При двух пальцах OpenLayers может также вычислять угол между ними.

Основные вычисления:

  • вектор между pointer-ами
  • арктангенс угла
  • разница углов между кадрами

Используется формула:

  • angle = atan2(y2 - y1, x2 - x1)

Изменение угла применяется к view.setRotation().


Pointer Capture и стабильность жестов

Для корректной обработки touch OpenLayers использует setPointerCapture.

Это обеспечивает:

  • сохранение потока событий даже при выходе пальца за пределы canvas
  • отсутствие «обрывов» жестов
  • стабильную работу pinch и drag

Без pointer capture жесты становились бы фрагментированными при быстром движении пальца.


Inertia (кинетика движения)

После завершения касания может активироваться инерционное перемещение карты.

Принцип работы:

  • измеряется скорость последнего движения pointer
  • после pointerup создаётся анимационный цикл
  • скорость постепенно уменьшается (deceleration)

Модель поведения:

  • высокая скорость → длительное движение карты
  • низкая скорость → быстрое затухание

Interaction API и touch-совместимые компоненты

Touch-поведение в OpenLayers управляется через набор стандартных interaction-классов:

  • DragPan
  • PinchZoom
  • PinchRotate
  • MouseWheelZoom (частично перекрывается с touchpad жестами)

Каждый interaction подписывается на pointer-события карты и фильтрует их по условиям:

  • количество pointer-ов
  • состояние клавиш модификаторов
  • активность других interactions

Приоритеты взаимодействий

OpenLayers использует систему блокировок и приоритетов:

  • один жест может «захватить» pointer stream
  • другие interactions временно деактивируются
  • предотвращается конфликт pan + pinch

Например:

  • при активном pinch zoom блокируется drag pan
  • при удержании одного pointer активируется только pan interaction

Multi-touch и управление несколькими pointer

Каждый pointer имеет уникальный pointerId.

Система хранит:

  • активные pointer-ы в Map
  • координаты каждого pointer-а
  • историю перемещений

Это позволяет:

  • корректно вычислять жесты
  • обрабатывать сложные сценарии (быстрое добавление/удаление пальцев)
  • избегать «скачков» состояния

Координатные преобразования при touch

Каждое touch-событие проходит трансформацию:

  1. screen pixel (clientX, clientY)
  2. viewport pixel
  3. map coordinate (EPSG projection)

OpenLayers использует:

  • map.getCoordinateFromPixel()
  • map.getPixelFromCoordinate()

При touch эти преобразования происходят на каждом pointermove, что критично для плавности взаимодействия.


Touchpad vs Touchscreen

Несмотря на схожесть жестов, OpenLayers различает:

  • pointerType = "touch" — сенсорный экран
  • pointerType = "mouse" — мышь
  • pointerType = "pen" — стилус

Touchpad на ноутбуках часто эмулирует pointer события, но поведение может отличаться:

  • pinch может интерпретироваться как wheel event
  • rotation может отсутствовать
  • ускорение жестов зависит от браузера

Оптимизация touch-интеракций

Для стабильной работы на мобильных устройствах важны следующие аспекты:

  • минимизация перерасчётов геометрии на pointermove
  • использование requestAnimationFrame для отрисовки
  • кэширование view state
  • ограничение частоты обновлений при инерции

Особенно критично:

  • не выполнять тяжёлые операции в обработчиках pointer событий
  • избегать синхронных пересчётов слоёв карты

Особенности поведения на мобильных браузерах

Мобильные браузеры добавляют собственные слои обработки жестов:

  • Safari iOS может перехватывать жесты скролла
  • Android Chrome иногда комбинирует touch + wheel events
  • double-tap zoom конфликтует с pinch zoom

OpenLayers подавляет часть стандартного поведения браузера через:

  • preventDefault() в критических сценариях
  • настройку touch-action: none на контейнер карты

Touch и кастомные interactions

OpenLayers позволяет создавать собственные interaction, работающие с touch-потоком.

Базовый шаблон:

  • подписка на handleDownEvent
  • обработка handleDragEvent
  • завершение через handleUpEvent

Такие interactions могут реализовывать:

  • рисование на карте пальцем
  • выделение области жестом
  • кастомные multi-touch операции

Обработка конфликтов жестов

В системе interactions используется механизм stopEvent:

  • событие может быть поглощено одним interaction
  • остальные не получают доступ к pointer stream
  • предотвращаются конфликтующие трансформации карты

Роль View в touch-событиях

Все touch-интеракции в конечном итоге изменяют состояние View:

  • center
  • resolution
  • rotation

View выступает как единая точка истины, а touch-жесты являются лишь способом изменения его параметров.


Закономерности поведения touch в OpenLayers

  • один палец → линейное перемещение
  • два пальца → геометрические трансформации (scale + rotate)
  • скорость жестов влияет на инерцию
  • точка центра вычисляется динамически
  • все события проходят через pointer abstraction layer