Альтернативный текст и доступность в Mapbox GL JS
В веб-картографии на основе Mapbox GL JS вопрос доступности выходит за пределы визуального отображения данных. Альтернативный текст используется не только как замена изображения, но как способ передачи смысловой информации пользователям вспомогательных технологий, а также как дополнительный слой семантики для элементов интерфейса карты.
В контексте Mapbox GL JS отсутствует единый универсальный механизм «alt» для всей карты, поскольку карта формируется из множества слоёв, тайлов, векторных данных и DOM-элементов. Поэтому альтернативное описание реализуется на разных уровнях: через HTML-разметку, свойства слоёв, подписи символов и программную генерацию доступных атрибутов.
Альтернативный текст в веб-картах выполняет несколько функций:
В Mapbox GL JS основная сложность заключается в том, что визуальный
слой не является DOM-структурой по умолчанию. Отрисовка выполняется
через WebGL, поэтому стандартные атрибуты HTML (alt,
title) применимы только к отдельным компонентам,
создаваемым поверх карты.
Наиболее прямой способ задания альтернативного текста реализуется через HTML-маркеры. Такие маркеры представляют собой DOM-элементы, размещённые поверх карты.
const markerElement = document.createElement('div');
markerElement.className = 'custom-marker';
markerElement.setAttribute('role', 'img');
markerElement.setAttribute('aria-label', 'Маркер: центр города и административное здание');
new mapboxgl.Marker(markerElement)
.setLngLat([69.2401, 53.2145])
.addTo(map);
В данном случае aria-label заменяет визуальную
информацию, встроенную в иконку маркера. Атрибут role="img"
задаёт семантику изображения для вспомогательных технологий.
При использовании текстовых HTML-элементов внутри маркеров альтернативный текст часто дублирует содержимое:
const el = document.createElement('div');
el.innerText = 'A';
el.setAttribute('aria-label', 'Маркер A: точка интереса');
new mapboxgl.Marker(el)
.setLngLat([69.1, 53.2])
.addTo(map);
Векторные слои Mapbox GL JS используют декларативную модель. Для
символов (symbol layer) альтернативный текст частично реализуется через
текстовые поля (text-field), которые фактически становятся
визуальной подписью.
map.addLayer({
id: 'cities',
type: 'symbol',
source: 'cities-data',
layout: {
'text-field': ['get', 'name'],
'text-size': 12
}
});
Хотя text-field не является альтернативным текстом в
классическом HTML-смысле, он выполняет ту же функцию — передаёт смысл
объекта через текстовое представление.
Для повышения доступности используется дублирование данных в свойствах объектов:
map.addLayer({
id: 'pois',
type: 'symbol',
source: 'pois',
layout: {
'text-field': ['get', 'label']
}
});
Если данные содержат сложные визуальные иконки
(icon-image), рекомендуется обеспечивать текстовую
альтернативу через свойства источника.
Иконки в Mapbox GL JS задаются через sprite и свойство
icon-image. Эти изображения не имеют прямого
HTML-эквивалента, поэтому альтернативный текст не существует на уровне
WebGL.
map.addLayer({
id: 'restaurants',
type: 'symbol',
source: 'places',
layout: {
'icon-image': 'restaurant-15',
'icon-size': 1
}
});
Для компенсации отсутствия текстовой семантики используется:
text-field рядом с иконкой;title через интерактивные popups;aria-label на контейнере карты;Всплывающие окна часто становятся основным носителем расширенного описания объекта. Их содержимое может рассматриваться как расширенный альтернативный текст.
new mapboxgl.Popup()
.setLngLat([69.2401, 53.2145])
.setHTML(`
<div role="dialog" aria-label="Информация об объекте">
<h3>Административное здание</h3>
<p>Основной городской центр управления</p>
</div>
`)
.addTo(map);
Использование role="dialog" и aria-label
позволяет связать визуальное окно с семантическим описанием.
Сам контейнер Mapbox GL JS требует явного задания роли и описания, поскольку canvas-элемент не содержит семантики.
map.getCanvas().setAttribute('role', 'application');
map.getCanvas().setAttribute(
'aria-label',
'Интерактивная карта с объектами инфраструктуры и географическими данными'
);
При необходимости более детального описания применяется связанный текстовый блок:
<div id="map-description" hidden>
Карта отображает города, дороги и точки интереса региона.
</div>
map.getCanvas().setAttribute('aria-describedby', 'map-description');
При работе с GeoJSON альтернативные описания часто формируются из свойств объекта.
map.addSource('locations', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
name: 'Парк культуры',
description: 'Городской парк с зелёной зоной и аллеями'
},
geometry: {
type: 'Point',
coordinates: [69.3, 53.2]
}
}
]
}
});
На основе этих данных формируется доступный текст:
const feature = features[0];
const altText = `${feature.properties.name}. ${feature.properties.description}`;
Такой подход позволяет синхронизировать визуальное представление и смысловую нагрузку.
При взаимодействии с объектами карты важно формировать текстовое
описание через события click или
mouseenter.
map.on('click', 'pois', (e) => {
const feature = e.features[0];
const description = feature.properties.description;
const announcement = document.getElementById('screen-reader-output');
announcement.textContent = description;
});
<div id="screen-reader-output" aria-live="polite"></div>
aria-live="polite" обеспечивает озвучивание изменений
без прерывания текущего фокуса.
В случаях сложных визуализаций используется параллельный скрытый слой DOM, содержащий структурированное описание карты.
<ul id="map-accessibility-layer" hidden>
<li>Объект: мост через реку</li>
<li>Объект: железнодорожная станция</li>
<li>Объект: жилой район</li>
</ul>
Такой слой может синхронизироваться с данными карты и обновляться при изменении viewport.
Архитектура WebGL накладывает ряд ограничений:
По этой причине альтернативный текст в Mapbox GL JS всегда является результатом проектирования, а не встроенной функции библиотеки.
Для сложных картографических систем применяется подход разделения данных и представления:
Такое разделение позволяет формировать полную текстовую эквивалентность визуальной карты без потери структуры данных.