Screen reader поддержка

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

MapLibre GL JS формирует карту на основе WebGL-рендеринга, что изначально ориентировано на визуальное представление. Поэтому поддержка screen reader реализуется не через прямое «прочтение карты», а через набор вспомогательных механизмов:

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

Основной принцип заключается в разделении визуального рендеринга и доступного представления данных.


Контейнер карты и семантика ARIA

Корневой элемент карты MapLibre GL JS обычно представляет собой div, внутри которого создаётся WebGL canvas. Для screen reader этот контейнер должен быть интерпретируемым как интерактивный виджет.

Базовая доступность обеспечивается через ARIA-атрибуты:

  • role="application" — обозначает сложный интерактивный компонент
  • aria-label — описание назначения карты
  • tabindex="0" — включение в порядок фокусировки

Пример конфигурации:

<div id="map"
     role="application"
     aria-label="Интерактивная карта"
     tabindex="0"></div>

Важно учитывать, что role="application" изменяет поведение screen reader: он переключается в режим приложения и перестаёт автоматически интерпретировать вложенные элементы как обычный документ.

При необходимости можно использовать более мягкий вариант:

  • role="region" для менее интерактивных сценариев
  • aria-roledescription="карта" для уточнения смысла

Навигация с клавиатуры

MapLibre GL JS не предоставляет полноценной навигации по карте «из коробки» в стиле DOM-элементов, поскольку географическое пространство не имеет линейной структуры.

Типовой подход включает:

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

Пример логики управления:

map.getCanvas().addEventListener('keydown', (e) => {
  const step = 100;

  switch (e.key) {
    case 'ArrowUp':
      map.panBy([0, -step]);
      break;
    case 'ArrowDown':
      map.panBy([0, step]);
      break;
    case 'ArrowLeft':
      map.panBy([-step, 0]);
      break;
    case 'ArrowRight':
      map.panBy([step, 0]);
      break;
    case '+':
      map.zoomIn();
      break;
    case '-':
      map.zoomOut();
      break;
  }
});

Ключевая особенность: клавиатурная модель должна быть согласована с ожиданиями screen reader, иначе пользователь теряет контекст перемещения.


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

Фокус в карте не должен «теряться» при взаимодействии с объектами слоя, всплывающими окнами и контролами.

Типовая стратегия:

  • фиксированный фокус на контейнере карты
  • временная передача фокуса в popup или панель информации
  • возврат фокуса обратно в карту после закрытия

Для управления состоянием используются:

  • focus() на DOM-элементах
  • программное отслеживание открытия/закрытия popup
  • сохранение последнего активного объекта

Пример:

popup.on('open', () => {
  popup.getElement().setAttribute('tabindex', '-1');
  popup.getElement().focus();
});

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

Оповещения для screen reader (aria-live)

Динамическое изменение карты не отражается автоматически в screen reader. Перемещение карты, изменение масштаба или выбор объекта требует явных текстовых уведомлений.

Для этого используется aria-live регион:

<div id="map-announcements"
     aria-live="polite"
     aria-atomic="true"
     class="sr-only"></div>

Обновление состояния:

function announce(message) {
  const el = document.getElementById('map-announcements');
  el.textContent = message;
}

Примеры сообщений:

  • «Карта перемещена на район центра города»
  • «Масштаб увеличен до уровня улиц»
  • «Выбран объект: станция метро»

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


Описание слоев и объектов

Географические слои в MapLibre GL JS (GeoJSON, vector tiles, raster overlays) не имеют встроенной текстовой семантики, поэтому необходимо формировать описания вручную.

Каждый интерактивный объект должен иметь:

  • имя или тип
  • географический контекст
  • краткое описание
  • координаты или относительное положение

Пример обработки клика:

map.on('click', 'cities-layer', (e) => {
  const feature = e.features[0];

  const description = `
    Город ${feature.properties.name}.
    Население: ${feature.properties.population}.
  `;

  announce(description);
});

Для screen reader важна не только информация, но и её структура. Длинные описания необходимо разбивать на логические блоки.


Работа с всплывающими окнами (popups)

Popup-элементы в MapLibre GL JS являются визуальными слоями поверх карты и требуют отдельной доступности.

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

  • role="dialog" для контейнера popup
  • aria-labelledby для заголовка
  • aria-describedby для основного текста
  • управление фокусом внутри popup

Пример:

<div class="popup"
     role="dialog"
     aria-labelledby="popup-title"
     aria-describedby="popup-content">
</div>

При открытии popup:

  • фокус переводится внутрь
  • клавиша Escape закрывает окно
  • после закрытия фокус возвращается на карту

Кастомные контролы и доступность

Элементы управления картой (zoom, fullscreen, layers switcher) часто реализуются как кастомные компоненты.

Для корректной работы screen reader необходимо:

  • использовать button вместо div
  • добавлять aria-label
  • обеспечивать клавиатурную активацию через Enter и Space

Пример контролла:

<button aria-label="Увеличить масштаб">
  +
</button>

Если контрол визуально сложный, требуется:

  • скрытый текст (sr-only)
  • явное состояние (aria-pressed, aria-expanded)

Альтернативные представления данных

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

Распространённые подходы:

  • список объектов текущей области карты
  • таблица точек интереса
  • текстовое описание маршрута
  • фильтруемый список слоёв

Пример структуры списка:

<ul id="map-list">
  <li>Станция метро: Центральная</li>
  <li>Парк: Городской сад</li>
  <li>Музей: Исторический центр</li>
</ul>

Список синхронизируется с состоянием карты: при перемещении обновляется набор элементов.


Практические паттерны интеграции

Сочетание MapLibre GL JS и screen reader поддержки обычно строится по архитектурному принципу «двух каналов»:

  • визуальный канал (WebGL карта)
  • семантический канал (DOM + ARIA)

Ключевые паттерны:

1. Синхронизация состояния

Каждое изменение карты должно иметь текстовый эквивалент.

2. Декларативные события

События MapLibre (moveend, zoomend, click) преобразуются в текстовые уведомления.

3. Изоляция WebGL

Canvas не содержит семантики и не участвует в accessibility tree.

4. Явные переходы контекста

Любая смена режима (например, фильтрация слоёв) сопровождается объявлением.


Производительность и доступность

Добавление accessibility-слоя увеличивает количество DOM-операций и событий. При больших наборах геоданных важно учитывать:

  • частоту обновлений aria-live
  • дебаунс уведомлений при перемещении карты
  • ограничение числа одновременно активных объектов
  • кэширование текстовых описаний features

Особенно критичны ситуации, когда move события вызывают постоянные обновления текста. В таких случаях используется:

  • requestAnimationFrame для сглаживания
  • задержка уведомлений после moveend
  • агрегация сообщений в одно описание состояния карты

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