ARIA (Accessible Rich Internet Applications) представляет собой набор атрибутов, расширяющих семантику HTML для вспомогательных технологий. В контексте интерактивных карт на основе WebGL, таких как MapLibre GL JS, ARIA становится критическим слоем между графическим рендерингом и программной интерпретацией интерфейса.
Карты в браузере представляют собой сложный Canvas/WebGL-контент, который по своей природе не имеет семантики DOM. Без дополнительного описания элементы управления, маркеры, всплывающие окна и слои остаются «невидимыми» для экранных дикторов. ARIA-атрибуты компенсируют этот разрыв.
Ключевая особенность MapLibre GL JS — отделение визуального слоя (WebGL) от интерактивного DOM-слоя управления, что требует явного описания всех доступных пользователю действий.
Корневой контейнер карты в MapLibre GL JS обычно представляет собой
<div> с вложенным <canvas>. Сам
canvas не содержит семантики, поэтому ARIA-атрибуты применяются на
уровне контейнера.
Типичная конфигурация:
<div id="map"
role="application"
aria-label="Интерактивная карта"
tabindex="0"></div>
Разбор ключевых атрибутов:
role=“application” Указывает вспомогательным технологиям, что элемент содержит сложное интерактивное приложение. Это отключает часть стандартной навигации по документу.
aria-label Определяет текстовое описание карты. Без него экранный диктор не сможет интерпретировать назначение области.
tabindex=“0” Делает карту фокусируемой, что позволяет управлять клавиатурной навигацией.
При инициализации карты создаётся WebGL-контекст, но DOM-структура остаётся минимальной. Это означает, что доступность должна быть добавлена программно:
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [0, 0],
zoom: 2
});
После создания экземпляра важно дополнительно описывать доступность контейнера:
const container = map.getContainer();
container.setAttribute('role', 'application');
container.setAttribute('aria-label', 'Географическая карта мира');
container.setAttribute('tabindex', '0');
MapLibre GL JS не навязывает ARIA-структуру, поэтому ответственность за семантику лежит на разработчике.
MapLibre GL JS поддерживает навигацию через клавиатуру, но её доступность должна быть описана через ARIA-связи.
Основные взаимодействия:
Для описания можно использовать:
<div id="map"
role="application"
aria-label="Карта. Управление стрелками для перемещения, плюс и минус для масштаба"
aria-describedby="map-help">
</div>
<div id="map-help" hidden>
Используются стрелки для перемещения карты, клавиши плюс и минус для изменения масштаба.
</div>
aria-describedby связывает карту с расширенным описанием поведения, которое может быть озвучено экранным диктором.
MapLibre GL JS позволяет добавлять кастомные контролы через интерфейс
IControl. Каждый такой элемент должен иметь корректную
семантику.
Пример структуры кнопки управления масштабом:
class ZoomControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'maplibregl-ctrl maplibregl-ctrl-group';
this.zoomInButton = document.createElement('button');
this.zoomInButton.type = 'button';
this.zoomInButton.innerHTML = '+';
this.zoomInButton.setAttribute('aria-label', 'Приблизить карту');
this.zoomOutButton = document.createElement('button');
this.zoomOutButton.type = 'button';
this.zoomOutButton.innerHTML = '−';
this.zoomOutButton.setAttribute('aria-label', 'Отдалить карту');
this.container.appendChild(this.zoomInButton);
this.container.appendChild(this.zoomOutButton);
this.zoomInButton.addEventListener('click', () => map.zoomIn());
this.zoomOutButton.addEventListener('click', () => map.zoomOut());
return this.container;
}
onRemove() {
this.container.remove();
this.map = undefined;
}
}
ARIA-аспекты:
type="button" для предотвращения
сабмита формМаркеры в MapLibre GL JS часто реализуются как DOM-элементы, что делает их основными кандидатами для ARIA-описания.
const el = document.createElement('div');
el.className = 'marker';
el.setAttribute('role', 'button');
el.setAttribute('tabindex', '0');
el.setAttribute('aria-label', 'Маркер: офис компании');
Добавление интерактивности:
el.addEventListener('click', () => {
new maplibregl.Popup()
.setLngLat([30, 50])
.setHTML('<strong>Офис</strong>')
.addTo(map);
});
Семантическая модель маркера:
Popup-окна требуют отдельной семантической обработки, поскольку они появляются динамически и должны быть доступны для экранных дикторов сразу после открытия.
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true
})
.setLngLat([30, 50])
.setHTML('<div role="dialog" aria-label="Информация о точке">Описание объекта</div>')
.addTo(map);
Важные моменты:
Дополнительная обработка фокуса:
popup.getElement().setAttribute('tabindex', '-1');
popup.getElement().focus();
Слои в MapLibre GL JS являются визуальной абстракцией и не имеют DOM-представления. Однако их состояние может быть описано через ARIA-метки в пользовательском интерфейсе управления слоями.
Пример панели слоёв:
<div role="region" aria-label="Панель управления слоями">
<button aria-pressed="true">Дороги</button>
<button aria-pressed="false">Здания</button>
</div>
aria-pressed используется для переключаемых состояний:
При интеграции с MapLibre:
function toggleLayer(id, visible) {
map.setLayoutProperty(id, 'visibility', visible ? 'visible' : 'none');
}
MapLibre GL JS активно обновляет содержимое карты: перемещение, изменение масштаба, появление объектов. Эти изменения должны быть частично отражены через ARIA live regions.
<div aria-live="polite" id="map-status"></div>
Обновление состояния:
map.on('moveend', () => {
const center = map.getCenter();
document.getElementById('map-status').textContent =
`Карта перемещена. Центр: ${center.lng.toFixed(2)}, ${center.lat.toFixed(2)}`;
});
aria-live=“polite” обеспечивает ненавязчивое обновление информации для экранных дикторов.
Фокусировка является критическим аспектом доступности карт. MapLibre GL JS не управляет фокусом автоматически, поэтому требуется явное управление.
Основные принципы:
Пример возврата фокуса:
const previousFocus = document.activeElement;
popup.on('close', () => {
if (previousFocus) {
previousFocus.focus();
}
});
1. Отсутствие role на контейнере карты Без role=“application” или альтернативного описания карта воспринимается как пустой блок.
2. Использование только визуальных маркеров DOM-элементы без aria-label полностью недоступны для вспомогательных технологий.
3. Потеря фокуса при взаимодействии с WebGL Canvas не сохраняет семантический фокус, требуется внешнее управление.
4. Отсутствие описания динамики Перемещение карты без aria-live делает изменения состояния «немыми».
5. Неправильное использование role=“application” Избыточное применение этого role может ухудшить навигацию по документу, если интерфейс карты не требует сложного взаимодействия.
MapLibre GL JS предоставляет стандартные элементы управления
(NavigationControl, ScaleControl,
FullscreenControl). Эти элементы уже содержат базовую
семантику, но при кастомизации требуется её сохранение.
Пример расширения стандартного контролла:
class CustomScaleControl extends maplibregl.ScaleControl {
onAdd(map) {
const container = super.onAdd(map);
container.setAttribute('aria-label', 'Масштаб карты');
return container;
}
}
При проектировании интерфейсов на MapLibre GL JS ARIA рассматривается не как дополнение, а как слой описания поведения:
Такой подход формирует единый семантический слой поверх WebGL-рендеринга, позволяя вспомогательным технологиям интерпретировать пространственные данные как структурированный интерфейс.