Интерактивные веб-карты относятся к наиболее сложным интерфейсам с точки зрения доступности. Визуальная природа данных, высокая плотность информации, непрерывная динамика и многослойность создают значительные барьеры для экранных считывателей.
MapLibre GL JS формирует карту на основе WebGL-рендеринга, что изначально ориентировано на визуальное представление. Поэтому поддержка screen reader реализуется не через прямое «прочтение карты», а через набор вспомогательных механизмов:
Основной принцип заключается в разделении визуального рендеринга и доступного представления данных.
Корневой элемент карты 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, иначе пользователь теряет контекст перемещения.
Фокус в карте не должен «теряться» при взаимодействии с объектами слоя, всплывающими окнами и контролами.
Типовая стратегия:
Для управления состоянием используются:
focus() на DOM-элементахПример:
popup.on('open', () => {
popup.getElement().setAttribute('tabindex', '-1');
popup.getElement().focus();
});
popup.on('close', () => {
map.getCanvas().focus();
});
Динамическое изменение карты не отражается автоматически в 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 важна не только информация, но и её структура. Длинные описания необходимо разбивать на логические блоки.
Popup-элементы в MapLibre GL JS являются визуальными слоями поверх карты и требуют отдельной доступности.
Рекомендуемая структура:
role="dialog" для контейнера popuparia-labelledby для заголовкаaria-describedby для основного текстаПример:
<div class="popup"
role="dialog"
aria-labelledby="popup-title"
aria-describedby="popup-content">
</div>
При открытии popup:
Escape закрывает окноЭлементы управления картой (zoom, fullscreen, layers switcher) часто реализуются как кастомные компоненты.
Для корректной работы screen reader необходимо:
button вместо divaria-labelEnter и
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 поддержки обычно строится по архитектурному принципу «двух каналов»:
Ключевые паттерны:
1. Синхронизация состояния
Каждое изменение карты должно иметь текстовый эквивалент.
2. Декларативные события
События MapLibre (moveend, zoomend,
click) преобразуются в текстовые уведомления.
3. Изоляция WebGL
Canvas не содержит семантики и не участвует в accessibility tree.
4. Явные переходы контекста
Любая смена режима (например, фильтрация слоёв) сопровождается объявлением.
Добавление accessibility-слоя увеличивает количество DOM-операций и событий. При больших наборах геоданных важно учитывать:
aria-liveОсобенно критичны ситуации, когда move события вызывают
постоянные обновления текста. В таких случаях используется:
requestAnimationFrame для сглаживанияmoveendБаланс между точностью и частотой обновлений определяет удобство взаимодействия с экранными считывателями.