Клавиатурная навигация

Клавиатурная навигация в Leaflet основана на встроенном обработчике L.Map.Keyboard, который перехватывает события клавиатуры, когда контейнер карты находится в фокусе, и преобразует их в действия панорамирования и масштабирования. Этот механизм работает поверх стандартной системы событий браузера и тесно связан с управлением фокусом DOM-элемента карты.

По умолчанию клавиатурная навигация активна, если карта создана без явного отключения соответствующей опции.

const map = L.map('map', {
    keyboard: true
});

Опция keyboard управляет тем, будет ли экземпляр L.Map регистрировать обработчик клавиатуры. При значении false карта полностью игнорирует нажатия клавиш, даже если контейнер находится в фокусе.

Отключение после инициализации:

map.keyboard.disable();

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

map.keyboard.enable();

Базовые клавиши управления

Leaflet использует фиксированный набор клавиш, привязанный к стандартным действиям:

  • Стрелки (← ↑ → ↓) — панорамирование карты
  • + и - — увеличение и уменьшение масштаба
  • Home — возврат к начальному виду (если задан setView)
  • End — переход к максимальному доступному состоянию (зависит от конфигурации и слоёв)

Панорамирование выполняется дискретными шагами, величина которых задаётся через параметр конфигурации карты.

Шаг панорамирования

Интенсивность перемещения карты при нажатии клавиш регулируется параметром:

const map = L.map('map', {
    keyboard: true,
    keyboardPanDelta: 80
});

keyboardPanDelta определяет количество пикселей, на которое сдвигается карта при одном нажатии стрелки. Чем больше значение, тем быстрее происходит перемещение.

Механизм обработки фокуса

Клавиатурная навигация работает только тогда, когда контейнер карты находится в фокусе. Leaflet устанавливает tabIndex на элемент карты, чтобы он стал фокусируемым:

const map = L.map('map', {
    keyboard: true,
    tabIndex: 0
});

tabIndex: 0 позволяет элементу участвовать в естественном порядке фокуса страницы.

Получение фокуса программно:

map.getContainer().focus();

При потере фокуса обработка клавиш прекращается автоматически, что предотвращает конфликт с другими элементами интерфейса.

Внутренний обработчик L.Map.Keyboard

Внутри Leaflet используется объект L.Map.Keyboard, который подписывается на события keydown и интерпретирует коды клавиш.

Упрощённая логика:

map.keyboard = new L.Map.Keyboard(map);

map.keyboard._onKeyDown = function (e) {
    switch (e.keyCode) {
        case 37: map.panBy([-this._map.keyboardPanDelta, 0]); break;
        case 38: map.panBy([0, -this._map.keyboardPanDelta]); break;
        case 39: map.panBy([this._map.keyboardPanDelta, 0]); break;
        case 40: map.panBy([0, this._map.keyboardPanDelta]); break;
        case 187: map.zoomIn(); break;
        case 189: map.zoomOut(); break;
    }
};

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

Блокировка клавиш в формах

Leaflet автоматически игнорирует события, если фокус находится внутри интерактивных элементов:

  • <input>
  • <textarea>
  • <select>

Это предотвращает конфликт между вводом текста и управлением картой.

Логика проверки основана на document.activeElement:

const el = document.activeElement;
const isInput = el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA');

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

Полное отключение клавиатурной навигации:

const map = L.map('map', {
    keyboard: false
});

Частичная кастомизация через отключение стандартного обработчика и регистрацию собственного:

map.keyboard.disable();

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

Такой подход позволяет полностью заменить стандартную модель управления.

Обработка комбинаций клавиш

Leaflet не предоставляет встроенной системы сложных комбинаций (например, Ctrl + стрелка), однако такие сценарии реализуются через проверку модификаторов события:

map.getContainer().addEventListener('keydown', function (e) {
    if (e.ctrlKey && e.key === '+') {
        map.zoomIn();
    }

    if (e.shiftKey && e.key === 'ArrowLeft') {
        map.panBy([-200, 0]);
    }
});

Важно учитывать, что стандартный обработчик Leaflet может перехватывать часть событий, поэтому при расширении логики его обычно отключают.

Взаимодействие с инерцией и анимацией

Клавиатурное панорамирование интегрировано с системой анимации карты. При каждом нажатии стрелки создаётся новое перемещение с учётом текущего состояния инерции.

Если включена инерция:

const map = L.map('map', {
    inertia: true
});

то последовательные нажатия клавиш могут суммировать движение, создавая плавный эффект смещения.

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

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

Leaflet добавляет базовые атрибуты:

  • role="application" или region (в зависимости от версии и конфигурации)
  • tabIndex для включения в поток фокуса

Дополнительные улучшения обычно включают явные подписи:

map.getContainer().setAttribute('aria-label', 'Интерактивная карта');

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

При клике вне карты или переключении вкладок браузера фокус автоматически снимается, и клавиатурные события больше не обрабатываются. Повторное включение управления требует возврата фокуса на контейнер:

window.addEventListener('load', () => {
    map.getContainer().focus();
});

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

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

Коды клавиш могут различаться:

  • + может приходить как 187 или как =
  • - может быть 189 или underscore в некоторых раскладках
  • стрелки стандартизированы, но могут требовать preventDefault для предотвращения прокрутки страницы

Leaflet нормализует часть этих различий, но при кастомной обработке требуется учитывать кроссбраузерные нюансы:

map.getContainer().addEventListener('keydown', function (e) {
    if (e.key === '+' || e.keyCode === 187) {
        map.zoomIn();
        e.preventDefault();
    }
});

Приоритет событий и конфликт с прокруткой страницы

При активной карте клавиши стрелок могут конфликтовать со скроллом страницы. Leaflet предотвращает это, вызывая preventDefault() внутри обработчика, если событие признано относящимся к управлению картой.

При отключённом стандартном обработчике такая защита отсутствует и должна реализовываться вручную.

map.getContainer().addEventListener('keydown', function (e) {
    if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
        e.preventDefault();
        map.panBy(/* ... */);
    }
});

Интеграция с другими слоями управления

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

  • жестами мыши (dragging)
  • тач-событиями
  • кнопками зума (L.Control.Zoom)

Все эти механизмы используют единый интерфейс изменения состояния карты (setView, panBy, zoomIn, zoomOut), что обеспечивает согласованное поведение независимо от источника ввода.