Touchstart, touchend, touchmove

Mapbox GL JS предоставляет собственную систему событий поверх WebGL-карты, включая поддержку сенсорных взаимодействий. На мобильных устройствах ключевыми становятся touchstart, touchmove и touchend, которые позволяют отслеживать начало касания, его перемещение и завершение.

Эти события интегрированы в систему событий карты и регистрируются через map.on(...), а не через стандартные DOM-обработчики, что обеспечивает синхронизацию с внутренним состоянием рендеринга и жестами карты (панорамирование, масштабирование, вращение).


Архитектура сенсорных событий

Mapbox GL JS обрабатывает касания через абстракцию событий карты. При этом каждый touch-событие содержит:

  • type — тип события (touchstart, touchmove, touchend)
  • target — экземпляр карты
  • point — координаты на экране (x, y)
  • lngLat — географические координаты
  • originalEvent — исходное DOM TouchEvent
  • features — (если используется queryRenderedFeatures) объекты под курсором

Ключевая особенность заключается в том, что Mapbox интерпретирует касания не как изолированные DOM-события, а как часть интерактивного состояния карты.


touchstart: начало взаимодействия

Событие touchstart возникает при первом касании экрана. Оно фиксирует момент, когда пользователь начинает взаимодействие с картой.

map.on('touchstart', (e) => {
    console.log('Touch start');
    console.log(e.point);
    console.log(e.lngLat);
});

Особенности поведения

  • Срабатывает при каждом новом контакте пальца с экраном
  • Может инициировать начало жеста панорамирования или зума
  • При мультитаче вызывается несколько раз (по числу точек касания)
  • Содержит данные о текущем положении пальцев

Практическое применение

  • Определение начала жеста перемещения карты
  • Фиксация точки старта для кастомных drag-операций
  • Подготовка данных для анализа жестов пользователя

touchmove: отслеживание перемещения

touchmove вызывается многократно при перемещении пальца по экрану. Это наиболее «нагруженное» событие с точки зрения производительности, так как может срабатывать десятки раз в секунду.

map.on('touchmove', (e) => {
    console.log('Touch move');
    console.log(e.point);
    console.log(e.lngLat);
});

Поведение и нюансы

  • Срабатывает непрерывно во время движения пальца
  • Может вызываться одновременно с внутренними обработчиками Mapbox (pan, rotate, zoom)
  • Частота зависит от устройства и браузера
  • Может конфликтовать с нативными жестами карты

Оптимизация обработки

Из-за высокой частоты вызовов важно минимизировать вычисления внутри обработчика:

map.on('touchmove', (e) => {
    requestAnimationFrame(() => {
        updateOverlay(e.lngLat);
    });
});

Использование requestAnimationFrame позволяет синхронизировать обновления с рендерингом и снизить нагрузку.


Использование для кастомных взаимодействий

touchmove часто применяется для реализации:

  • пользовательских drag-объектов на карте
  • рисования линий и полигонов
  • динамического обновления UI по позиции пальца
  • интерактивных измерительных инструментов

touchend: завершение касания

Событие touchend фиксирует момент, когда пользователь убирает палец с экрана.

map.on('touchend', (e) => {
    console.log('Touch end');
});

Характеристики

  • Срабатывает при завершении каждого касания
  • Может происходить несколько раз при мультитач-жестах
  • Завершает цепочку touchstart → touchmove → touchend
  • Может содержать финальные координаты жеста

Типичные сценарии

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

Взаимодействие с жестами карты

Mapbox GL JS по умолчанию использует touch-события для реализации встроенных жестов:

  • pan (перемещение карты)
  • pinch zoom (масштабирование)
  • rotate (вращение)

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


Отключение стандартных жестов

Для полного контроля над touch-событиями можно отключить интерактивность:

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v11',
    interactive: false
});

Либо выборочно отключить поведение:

map.dragPan.disable();
map.scrollZoom.disable();
map.touchZoomRotate.disable();

Сравнение touchstart, touchmove и touchend

touchstart

Фиксирует начальную точку взаимодействия. Используется для инициализации состояния.

touchmove

Отслеживает непрерывное движение. Основной канал динамических обновлений.

touchend

Завершает взаимодействие и фиксирует результат.


Работа с мультитачом

На устройствах с поддержкой мультитач каждый палец генерирует отдельные touch-события. Mapbox агрегирует их в единый поток взаимодействия карты.

map.on('touchstart', (e) => {
    console.log(e.originalEvent.touches.length);
});

Поле touches позволяет определить количество активных касаний.


Доступ к исходному DOM событию

Через e.originalEvent можно получить доступ к нативному TouchEvent:

map.on('touchmove', (e) => {
    const touches = e.originalEvent.touches;
    console.log(touches);
});

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


Координаты и географическая привязка

Mapbox автоматически преобразует экранные координаты в географические:

  • e.point — пиксельные координаты относительно контейнера карты
  • e.lngLat — долгота и широта
map.on('touchstart', (e) => {
    const { lng, lat } = e.lngLat;
    console.log(lng, lat);
});

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


Практическая модель обработки жеста

Типичная логика обработки пользовательского жеста строится как конечный автомат:

  • touchstart — фиксация начальной точки
  • touchmove — обновление текущего состояния
  • touchend — финализация действия
let isDrawing = false;
let path = [];

map.on('touchstart', (e) => {
    isDrawing = true;
    path = [e.lngLat];
});

map.on('touchmove', (e) => {
    if (!isDrawing) return;
    path.push(e.lngLat);
});

map.on('touchend', () => {
    isDrawing = false;
    console.log(path);
});

Взаимодействие с производительностью WebGL-карты

Mapbox GL JS рендерит карту через WebGL, поэтому любые частые обновления в touchmove могут влиять на:

  • частоту кадров (FPS)
  • задержку отклика интерфейса
  • плавность анимаций

Оптимизация достигается через:

  • минимизацию вычислений внутри обработчиков
  • батчинг обновлений
  • использование requestAnimationFrame
  • ограничение частоты обработки (throttle)

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

Разные браузеры по-разному обрабатывают touch-события:

  • Safari iOS может агрегировать события
  • Android Chrome чаще генерирует более частые touchmove
  • некоторые устройства интерпретируют жесты как scroll

Mapbox компенсирует эти различия через собственный event layer, однако логика приложения должна учитывать вариативность поведения.


Конфликты с прокруткой страницы

При использовании карты внутри scroll-контейнера возможны конфликты между:

  • вертикальной прокруткой страницы
  • горизонтальным pan карты

Решение часто включает:

.map-container {
    touch-action: none;
}

Это отключает нативную обработку жестов браузером и передаёт управление карте.


Использование фильтрации событий

Иногда необходимо реагировать только на первичное касание:

map.on('touchstart', (e) => {
    if (e.originalEvent.touches.length > 1) return;
    console.log('Single touch');
});

Это позволяет разделять жесты одного пальца и мультитач.


Интеграция с кастомными слоями

Touch-события часто используются совместно с кастомными слоями Mapbox:

  • symbol layers
  • circle layers
  • custom WebGL layers
map.on('touchend', (e) => {
    const features = map.queryRenderedFeatures(e.point);
    console.log(features);
});

Такой подход позволяет реализовать интерактивные элементы карты без DOM-оверлеев.