Keyboard navigation

Клавиатурная навигация в веб-картах обеспечивает доступность интерфейса без использования мыши и критически важна для соответствия стандартам WCAG. В контексте Mapbox GL JS она реализуется через механизм фокуса, обработку событий клавиатуры и интеграцию с интерактивными слоями карты.

Базовая модель взаимодействия строится вокруг перехвата стандартных клавиш (стрелки, Tab, +/-) и преобразования их в действия камеры: панорамирование, масштабирование и вращение.


Базовое поведение клавиатуры в Mapbox GL JS

По умолчанию карта поддерживает следующие действия:

  • Стрелки ← ↑ → ↓ — сдвиг карты (pan)
  • + / - — увеличение и уменьшение масштаба
  • Shift + стрелки — более быстрый сдвиг
  • Shift + Alt + стрелки — вращение и наклон (если включено pitch/rotation)

Фокус на карте устанавливается через контейнер map.getCanvasContainer() и внутренние обработчики событий.

map.keyboard.enable();

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


Контрол Keyboard и его конфигурация

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

const keyboard = new mapboxgl.KeyboardHandler();

map.addControl({
  onAdd: function(map) {
    this.map = map;
    return document.createElement('div');
  },
  onRemove: function() {}
});

Однако в стандартной архитектуре используется встроенный контрол:

map.keyboard.enable();
map.keyboard.disable();

Управление состоянием клавиатурной навигации

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

Отключение взаимодействия

map.keyboard.disable();

Используется при:

  • открытых модальных окнах
  • вводе текста в форме поверх карты
  • необходимости блокировки взаимодействий

Повторное включение

map.keyboard.enable();

Фокусировка и tabindex

Карта становится доступной для клавиатуры только при наличии фокуса. В DOM-структуре Mapbox GL JS canvas получает tabindex, позволяющий включать его в порядок навигации.

map.getCanvas().setAttribute('tabindex', '0');
map.getCanvas().focus();

Без этого шага клавиши не будут обрабатываться.

Практика управления фокусом

Часто применяются стратегии:

  • автофокус при загрузке карты
  • фокус по клику на контейнер
  • возврат фокуса после закрытия UI-элементов
map.on('load', () => {
  map.getCanvas().focus();
});

Обработка клавиатурных событий

Низкоуровневое управление возможно через DOM-события:

map.getCanvas().addEventListener('keydown', (e) => {
  if (e.key === 'ArrowUp') {
    map.panBy([0, -50]);
  }
});

Этот подход используется для полного переопределения стандартного поведения.

Перехват событий

Важно учитывать:

  • Mapbox уже обрабатывает большинство клавиш
  • повторная обработка может конфликтовать с встроенными контролами
  • рекомендуется использовать e.preventDefault() при кастомной логике

Панорамирование и масштабирование через клавиатуру

Ключевые методы управления камерой:

map.panBy([dx, dy], { duration: 200 });
map.zoomIn();
map.zoomOut();

Привязка к клавишам:

map.getCanvas().addEventListener('keydown', (e) => {
  switch (e.key) {
    case '+':
      map.zoomIn();
      break;
    case '-':
      map.zoomOut();
      break;
  }
});

Вращение и наклон карты с клавиатуры

При включённых параметрах pitch и bearing доступно управление вращением:

map.rotateTo(map.getBearing() + 15);
map.easeTo({ pitch: map.getPitch() + 10 });

Комбинации клавиш часто используют модификаторы:

map.getCanvas().addEventListener('keydown', (e) => {
  if (e.shiftKey && e.key === 'ArrowLeft') {
    map.rotateTo(map.getBearing() - 10);
  }
});

Доступность интерактивных объектов

Клавиатурная навигация должна учитывать взаимодействие с объектами карты:

  • маркеры
  • popups
  • geojson features

Фокус на маркерах

const markerElement = marker.getElement();
markerElement.setAttribute('tabindex', '0');

markerElement.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    popup.addTo(map);
  }
});

Работа с popups и клавиатурой

Popups должны быть закрываемыми через клавиатуру:

popup.on('open', () => {
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      popup.remove();
    }
  });
});

Ключевой момент — корректное управление фокусом после закрытия:

popup.on('close', () => {
  map.getCanvas().focus();
});

Интеграция с Tab-навигацией страницы

Карта должна корректно вписываться в общий порядок tab-index на странице.

Рекомендуемая структура:

  • tabindex="0" для контейнера карты
  • логичный порядок интерактивных элементов
  • исключение конфликтов с формами и меню
document.querySelector('#map').setAttribute('tabindex', '0');

Кастомизация клавиатурных схем

В сложных приложениях стандартные бинды заменяются:

const keyMap = {
  ArrowUp: () => map.panBy([0, -100]),
  ArrowDown: () => map.panBy([0, 100]),
  ArrowLeft: () => map.panBy([-100, 0]),
  ArrowRight: () => map.panBy([100, 0])
};

map.getCanvas().addEventListener('keydown', (e) => {
  if (keyMap[e.key]) {
    keyMap[e.key]();
  }
});

Такой подход используется в:

  • GIS-приложениях
  • аналитических панелях
  • кастомных навигационных интерфейсах

Конфликты с браузерными и системными клавишами

Некоторые клавиши перехватываются браузером:

  • Space (прокрутка страницы)
  • Arrow keys (scroll)
  • PageUp/PageDown

Для корректной работы требуется:

e.preventDefault();
e.stopPropagation();

Доступность и ARIA-совместимость

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

  • role="application" для контейнера карты
  • описательные aria-label
  • управление фокусом при изменении состояния карты
map.getCanvas().setAttribute('role', 'application');
map.getCanvas().setAttribute('aria-label', 'Интерактивная карта');

Обработка конфликтов с текстовыми полями

Если карта находится рядом с input/textarea, требуется временное отключение управления:

input.addEventListener('focus', () => map.keyboard.disable());
input.addEventListener('blur', () => map.keyboard.enable());

Это предотвращает перехват стрелок и других клавиш.


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

Частые события keydown могут вызывать лишние пересчёты камеры. Оптимизация включает:

  • throttle обработчиков
  • использование встроенных методов panBy, easeTo
  • минимизацию DOM-операций внутри событий

Типовые архитектуры клавиатурного управления

1. Стандартная (Mapbox default)

  • встроенные бинды
  • минимальная кастомизация
  • высокая совместимость

2. Расширенная

  • добавление модификаторов
  • поддержка кастомных режимов (draw/edit)

3. Полностью кастомная

  • отключение map.keyboard.disable()
  • собственный event layer
  • полное управление камерой

Связь клавиатуры с анимацией камеры

Клавиатурные действия часто синхронизируются с анимацией:

map.easeTo({
  center: map.getCenter(),
  zoom: map.getZoom() + 1,
  duration: 300
});

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


Ограничения и особенности реализации

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

Совместимость с расширениями Mapbox GL JS

Клавиатурная навигация взаимодействует с:

  • NavigationControl
  • GeolocateControl
  • AttributionControl

и может быть перекрыта их внутренними обработчиками при активном фокусе.