Жесты и их обработка

В библиотеке Leaflet обработка жестов построена вокруг системы обработчиков (handlers), привязанных к экземпляру карты. Каждый тип взаимодействия реализован как отдельный модуль, который может быть включён или отключён независимо.

Карта выступает центральным объектом, через который проходят все пользовательские действия: мышь, сенсорный экран, клавиатура. Внутри используется единая система событий, обеспечивающая согласованную модель поведения вне зависимости от устройства ввода.

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

  • низкоуровневых DOM-событий;
  • логических событий карты;
  • обработчиков жестов (handlers), управляющих состоянием взаимодействия.

Система обработчиков жестов

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

  • инициализация (addHooks);
  • активация;
  • деактивация;
  • очистка (removeHooks).

Типичный набор встроенных обработчиков карты:

  • dragging — перетаскивание карты;
  • scrollWheelZoom — масштабирование колесом мыши;
  • doubleClickZoom — масштабирование двойным кликом;
  • boxZoom — масштабирование выделением области;
  • keyboard — управление с клавиатуры;
  • touchZoom — pinch-жесты на сенсорных экранах;
  • tap — оптимизация тапа на мобильных устройствах.

Каждый обработчик может быть включён или отключён через API карты:

map.dragging.enable();
map.scrollWheelZoom.disable();

Базовые жесты карты

Перетаскивание (dragging)

Перетаскивание является основным способом навигации. Оно работает через отслеживание последовательности событий mousedown → mousemove → mouseup или их сенсорных аналогов.

Внутренний процесс включает:

  • фиксацию начальной точки;
  • вычисление смещения;
  • применение трансформации к тайлам карты;
  • инерционное продолжение движения при отпускании.

Поддерживается инерция, задаваемая параметрами карты:

  • inertia
  • inertiaDeceleration
  • inertiaMaxSpeed
  • easeLinearity

Инерционное движение создаёт эффект «скольжения» после завершения жеста.

Масштабирование колесом мыши

Обработчик scrollWheelZoom преобразует вращение колеса в изменение zoom level.

Механизм включает:

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

Дополнительные параметры:

  • wheelDebounceTime — задержка между событиями;
  • wheelPxPerZoomLevel — чувствительность.

Сенсорные жесты

Pinch-zoom (touchZoom)

Жест pinch реализуется через обработчик touchZoom. Он отслеживает одновременно несколько touch-точек и вычисляет расстояние между ними.

Алгоритм:

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

Важный аспект — центр жеста, который вычисляется как середина между точками касания. Именно эта точка становится якорем масштабирования.

Tap и двойное нажатие

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

Двойной клик (doubleClickZoom) обрабатывается отдельно и может:

  • увеличивать масштаб;
  • центрировать карту в точке клика.

Параметры:

  • doubleClickZoom: 'center' | 'around' | false

События карты и их цепочки

Жесты в Leaflet транслируются в высокоуровневые события карты.

Основные события взаимодействия:

  • mousedown, mouseup, mousemove
  • touchstart, touchmove, touchend
  • click, dblclick
  • zoomstart, zoom, zoomend
  • movestart, move, moveend
  • dragstart, drag, dragend

Цепочка событий при перетаскивании:

  1. mousedown
  2. dragstart
  3. серия drag
  4. dragend
  5. moveend

Цепочка масштабирования:

  1. zoomstart
  2. zoom
  3. zoomend

Каждое событие содержит контекст:

  • координаты (latlng);
  • пиксельные координаты (containerPoint);
  • уровень масштаба (zoom).

Управление поведением жестов через опции карты

Поведение жестов настраивается через параметры при создании карты:

const map = L.map('map', {
  dragging: true,
  touchZoom: true,
  scrollWheelZoom: true,
  doubleClickZoom: true,
  boxZoom: true,
  keyboard: true
});

Дополнительные параметры:

  • tapTolerance — допустимое смещение при тапе;
  • touchZoom — включение pinch-жестов;
  • bounceAtZoomLimits — «отскок» на границах масштабирования;
  • worldCopyJump — логика перехода через границу мира.

Отключение жестов часто используется для статичных карт или встроенных мини-карт:

map.dragging.disable();
map.scrollWheelZoom.disable();

L.DomEvent и низкоуровневая обработка

Внутренний модуль L.DomEvent обеспечивает унифицированную работу с DOM-событиями.

Основные функции:

  • L.DomEvent.on(element, type, handler)
  • L.DomEvent.off(element, type, handler)
  • L.DomEvent.stopPropagation(e)
  • L.DomEvent.preventDefault(e)

Ключевая особенность — нормализация событий между браузерами и устройствами.

Также применяется система фильтрации событий:

  • игнорирование синтетических событий;
  • подавление конфликтов touch/mouse;
  • предотвращение «ghost clicks» на мобильных устройствах.

Предотвращение конфликтов жестов

При одновременном использовании нескольких типов ввода возникают конфликты:

  • touch против mouse;
  • scroll против drag;
  • pinch против double tap.

Для их разрешения используется:

  • блокировка событий на уровне DOM;
  • приоритет обработчиков;
  • временная деактивация конкурирующих handlers.

Пример: во время touchZoom блокируется dragging, чтобы исключить смещение карты.

Также применяется механизм tap tolerance, который различает клик и случайное смещение пальца.

Кастомные жесты и расширение системы

Система L.Handler позволяет создавать собственные жесты, интегрированные в жизненный цикл карты.

Структура кастомного обработчика:

L.CustomGesture = L.Handler.extend({
  addHooks: function () {
    L.DomEvent.on(this._map._container, 'pointerdown', this._onDown, this);
  },

  removeHooks: function () {
    L.DomEvent.off(this._map._container, 'pointerdown', this._onDown, this);
  },

  _onDown: function (e) {
    this._startPoint = e.clientX;
  }
});

После создания обработчик подключается к карте:

map.addHandler('customGesture', L.CustomGesture);
map.customGesture.enable();

Подобный подход позволяет реализовать:

  • жесты долгого нажатия;
  • свайпы;
  • кастомные мульти-тачи;
  • жесты управления UI поверх карты.

Работа с событиями жестов в прикладной логике

Жесты часто используются для синхронизации интерфейса с состоянием карты.

Примеры реакций:

  • блокировка UI при movestart;
  • обновление данных при moveend;
  • отображение индикатора загрузки при zoomstart;
  • оптимизация рендера при zoom.
map.on('movestart', () => {
  // фиксация состояния интерфейса
});

map.on('moveend', () => {
  // обновление данных по новому bbox
});

Мобильная специфика обработки жестов

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

  • объединение событий touch и pointer;
  • подавление стандартного скролла страницы;
  • увеличение чувствительности drag;
  • обработка множественных касаний.

Особое значение имеет параметр tap, который компенсирует задержку click-событий в мобильных браузерах.

Производительность при обработке жестов

Жесты напрямую связаны с частотой перерисовки карты. Основные оптимизации:

  • throttling событий move и zoom;
  • использование requestAnimationFrame;
  • минимизация DOM-операций во время drag;
  • кэширование тайлов при инерционном движении.

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

Также применяется разделение фаз:

  • input phase (события ввода);
  • update phase (пересчёт состояния);
  • render phase (обновление отображения).