ARIA атрибуты

Интерактивные карты, построенные на Mapbox GL JS, требуют особого внимания к доступности, поскольку большая часть функциональности представлена графически и не имеет семантического HTML-эквивалента. ARIA-атрибуты (Accessible Rich Internet Applications) компенсируют этот разрыв, позволяя экранным читалкам и вспомогательным технологиям интерпретировать карту и элементы управления корректно.

Карта в DOM обычно представляется как <canvas> или набор WebGL-слоёв внутри контейнера <div>. Без дополнительной разметки этот контейнер является «немым» элементом для вспомогательных технологий, поэтому ключевая задача — задать ему корректную семантику.


Семантическая роль контейнера карты

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

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

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

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

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


ARIA-описания состояния карты

Состояние карты динамично: масштаб, координаты, наклон, вращение. Эти изменения должны быть доступны для экранных читалок.

Часто используется связка:

  • aria-live="polite" — для ненавязчивых обновлений
  • aria-atomic="true" — для полного обновления текста состояния

Пример элемента состояния:

<div id="map-status"
     aria-live="polite"
     aria-atomic="true"></div>

При изменении карты (pan, zoom, rotate) обновляется текст:

map.on('moveend', () => {
  const center = map.getCenter();
  const zoom = map.getZoom();

  document.getElementById('map-status').textContent =
    `Центр: ${center.lat.toFixed(4)}, ${center.lng.toFixed(4)}. Масштаб: ${zoom.toFixed(2)}`;
});

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

Элементы управления (zoom, rotation, geolocate) в Mapbox GL JS создаются программно. Для них критично наличие ARIA-меток.

Кнопки масштабирования

map.addControl(new mapboxgl.NavigationControl({
  showCompass: true,
  showZoom: true
}));

После добавления DOM-элементы получают стандартные кнопки без достаточной семантики. Поэтому требуется уточнение:

  • aria-label="Приблизить"
  • aria-label="Отдалить"
  • aria-label="Сбросить поворот"

В кастомных реализациях:

<button aria-label="Приблизить карту">+</button>
<button aria-label="Отдалить карту">−</button>

Фокус и клавиатурная навигация

Mapbox GL JS поддерживает клавиатурное управление, но его поведение зависит от конфигурации контейнера.

Ключевые аспекты:

  • tabindex для входа в карту
  • обработка стрелок для перемещения
  • + / - для масштаба
  • Shift + стрелки для более грубого панорамирования

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

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

map.on('keydown', (e) => {
  if (e.originalEvent.key === '+') {
    map.zoomIn();
  }
  if (e.originalEvent.key === '-') {
    map.zoomOut();
  }
});

ARIA для маркеров и слоёв

Маркерные элементы часто реализуются как HTML-элементы поверх canvas. Они должны быть доступны:

  • role="button" для интерактивных маркеров
  • aria-label с названием точки
  • tabindex="0" для фокусировки

Пример:

<div class="marker"
     role="button"
     tabindex="0"
     aria-label="Офис компании"></div>

При создании через API:

const el = document.createElement('div');
el.setAttribute('role', 'button');
el.setAttribute('aria-label', 'Точка интереса');
el.tabIndex = 0;

Оповещения об изменении географического контекста

Карта постоянно меняет контекст: пользователь перемещается между регионами, слоями, объектами. Для передачи этих изменений используются ARIA-live регионы.

Типичные события:

  • изменение центра карты
  • выбор объекта
  • переключение слоя
  • поиск геолокации

Пример обработки выбора объекта:

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

  document.getElementById('map-status').textContent =
    `Выбран объект: ${feature.properties.name}`;
});

Доступность всплывающих окон (popups)

Popup-окна часто реализуются как отдельные DOM-элементы. Их необходимо снабжать:

  • role="dialog"
  • aria-labelledby
  • aria-describedby

Пример:

const popup = new mapboxgl.Popup({
  closeButton: true,
  closeOnClick: true
})
.setHTML(`
  <div role="dialog" aria-labelledby="popup-title">
    <h3 id="popup-title">Объект</h3>
    <p>Описание точки интереса</p>
  </div>
`);

Кнопка закрытия должна иметь:

  • aria-label="Закрыть"

Контроль контрастности и визуальных атрибутов

Хотя ARIA не отвечает за визуальные параметры, доступность тесно связана с:

  • достаточным контрастом элементов управления
  • видимым фокусом (:focus-visible)
  • отсутствием зависимости от цвета

Пример CSS:

.mapboxgl-ctrl button:focus-visible {
  outline: 3px solid #005fcc;
}

Атрибуты для отключения и состояний загрузки

При асинхронной загрузке карты и тайлов важно отражать состояние:

  • aria-busy="true" при загрузке
  • aria-disabled="true" для временно неактивных контролов
map.on('loading', () => {
  map.getContainer().setAttribute('aria-busy', 'true');
});

map.on('idle', () => {
  map.getContainer().setAttribute('aria-busy', 'false');
});

Доступность слоёв и легенды

Слои карты часто представлены визуально, но требуют текстового эквивалента:

  • легенда с описанием символов
  • ARIA-метки для переключателей слоёв
  • связь aria-controls между переключателем и содержимым

Пример:

<button aria-controls="layer-roads" aria-pressed="true">
  Дороги
</button>

<div id="layer-roads">
  Отображение дорожной сети
</div>

Интеграция ARIA с событиями Mapbox GL JS

События карты позволяют синхронизировать ARIA-состояния:

  • move
  • zoom
  • rotate
  • load
  • click

Пример универсального обновления статуса:

function updateStatus(text) {
  document.getElementById('map-status').textContent = text;
}

map.on('zoomend', () => {
  updateStatus(`Новый масштаб: ${map.getZoom().toFixed(2)}`);
});

map.on('rotateend', () => {
  updateStatus(`Поворот: ${map.getBearing().toFixed(1)}°`);
});

Ошибки при работе с ARIA в картографических интерфейсах

Типичные проблемы:

  • избыточное использование role="application" без клавиатурной поддержки
  • отсутствие aria-label у интерактивных элементов
  • дублирование живых регионов (aria-live) с высокой частотой обновлений
  • попытка описывать каждый пиксель карты, перегружая читалку

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


Структурирование доступного слоя интерфейса

Хорошо организованная карта строится слоями:

  1. Контейнер карты с семантической ролью
  2. ARIA-live регион состояния
  3. Контролы с корректными метками
  4. Маркеры как интерактивные элементы
  5. Попапы как диалоги
  6. Легенда как вспомогательная семантическая область

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