Прогрессивное улучшение (Progressive Enhancement) — подход к разработке веб-приложений, при котором базовая функциональность остаётся доступной максимально широкому кругу пользователей, а дополнительные возможности подключаются только при наличии поддержки необходимых технологий. В контексте Mapbox GL JS этот принцип особенно важен, поскольку библиотека активно использует WebGL, аппаратное ускорение графики и современные возможности браузеров.
Грамотное применение прогрессивного улучшения позволяет создавать картографические приложения, которые продолжают работать даже при отсутствии WebGL, ограниченной производительности устройства или частичной несовместимости браузера.
Интерактивные карты предъявляют повышенные требования к программной и аппаратной среде:
Игнорирование этих факторов приводит к ситуациям, когда карта полностью перестаёт работать на отдельных устройствах.
Например:
Прогрессивное улучшение помогает избежать полного отказа интерфейса.
Перед созданием карты рекомендуется убедиться, что текущая среда поддерживает работу библиотеки.
Mapbox GL JS предоставляет встроенный механизм проверки:
if (mapboxgl.supported()) {
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6176, 55.7558],
zoom: 10
});
}
Метод supported() анализирует:
Возвращаемое значение:
true
или
false
Если карта не поддерживается, интерфейс не должен оставаться пустым.
Распространённое решение:
<div id="map"></div>
<div id="fallback" hidden>
Карта недоступна в данном браузере.
</div>
if (mapboxgl.supported()) {
createMap();
} else {
document.getElementById('fallback').hidden = false;
}
Вместо текстового сообщения могут использоваться:
Часто интерактивность не является обязательной.
Если WebGL недоступен, можно показать заранее подготовленное изображение.
<img
src="city-map.png"
alt="Карта города"
>
Подобный подход обеспечивает:
Особенно полезно это для:
Даже если браузер поддерживает WebGL, запуск карты сразу после загрузки страницы может быть неоптимальным.
Хорошей практикой считается отложенное создание карты.
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
createMap();
observer.disconnect();
}
});
});
observer.observe(document.getElementById('map'));
Преимущества:
Необязательно подключать Mapbox GL JS на каждой странице приложения.
Можно загружать библиотеку только при необходимости.
async function loadMapbox() {
const script = document.createElement('script');
script.src =
'https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js';
document.body.appendChild(script);
await new Promise(resolve => {
script.onl oad = resolve;
});
}
После загрузки:
await loadMapbox();
createMap();
Такой подход особенно полезен в крупных SPA-приложениях.
Некоторые возможности могут быть слишком тяжёлыми для мобильных устройств.
Примеры ресурсоёмких элементов:
Проверка устройства может выглядеть следующим образом:
const isMobile =
/Android|iPhone|iPad/i.test(
navigator.userAgent
);
Для мобильных устройств:
if (!isMobile) {
map.addLayer({
id: '3d-buildings',
type: 'fill-extrusion',
source: 'composite',
'source-layer': 'building'
});
}
В результате пользователи мощных устройств получают расширенный функционал, а владельцы слабых устройств — стабильную работу.
Трёхмерная визуализация значительно увеличивает нагрузку на графическую подсистему.
Вместо автоматического отображения можно подключать её по запросу пользователя.
<button id="enable3d">
Включить 3D
</button>
document
.getElementById('enable3d')
.addEventListener('click', enable3D);
Преимущества:
Большие наборы геоданных способны серьёзно замедлять карту.
Плохой вариант:
map.addSource('objects', {
type: 'geojson',
data: hugeGeoJson
});
Лучше использовать поэтапную загрузку.
Например, загружать данные только для текущей области просмотра.
map.on('moveend', async () => {
const bounds = map.getBounds();
loadFeatures(bounds);
});
Такой подход уменьшает:
При большом количестве точек интерфейс может становиться медленным.
Кластеризация позволяет показывать агрегированные данные вместо тысяч отдельных объектов.
map.addSource('places', {
type: 'geojson',
data: places,
cluster: true,
clusterRadius: 50
});
Преимущества:
При увеличении масштаба карта постепенно раскрывает детали.
Не все пользователи нуждаются во всём наборе инструментов.
Базовая карта:
const map = new mapboxgl.Map({
container: 'map',
style: style
});
Расширенные контролы:
map.addControl(
new mapboxgl.NavigationControl()
);
map.addControl(
new mapboxgl.FullscreenControl()
);
map.addControl(
new mapboxgl.ScaleControl()
);
Некоторые элементы можно добавлять только после взаимодействия пользователя.
Геолокация зависит от:
Поэтому её следует считать дополнительной возможностью.
if ('geolocation' in navigator) {
map.addControl(
new mapboxgl.GeolocateControl()
);
}
Отсутствие геолокации не должно нарушать основную функциональность карты.
Рельеф требует дополнительных ресурсов.
Подключение может выполняться после успешной загрузки карты.
map.on('load', () => {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: 1.5
});
});
Для старых устройств:
if (isPowerfulDevice) {
enableTerrain();
}
Подобная стратегия уменьшает риск проблем с производительностью.
Современные браузеры позволяют получать сведения о сети.
const connection =
navigator.connection;
Проверка скорости:
if (
connection &&
connection.effectiveType === '2g'
) {
disableHeavyFeatures();
}
Можно отключать:
Загрузка стиля может завершиться ошибкой.
Например:
Обработка ошибки:
map.on('error', event => {
console.error(event.error);
showFallbackMessage();
});
Интерфейс должен сохранять работоспособность даже при невозможности загрузить карту.
Создание карты занимает некоторое время.
До завершения загрузки можно отображать заглушку.
<div id="loading">
Загрузка карты...
</div>
<div id="map"></div>
После полной инициализации:
map.on('load', () => {
loading.remove();
});
Пользователь получает визуальную обратную связь вместо пустого контейнера.
Удобно выделять несколько уровней поддержки.
Такой подход позволяет масштабировать функциональность в зависимости от возможностей платформы.
Часто используется следующая последовательность:
Схематично процесс можно представить так:
renderFallback();
if (mapboxgl.supported()) {
createMap();
loadCoreData();
if (fastConnection) {
enableAdvancedLayers();
}
if (powerfulDevice) {
enable3D();
}
}
Подобная архитектура обеспечивает устойчивую работу приложения в широком диапазоне условий эксплуатации, сохраняя доступность основных картографических функций и постепенно расширяя возможности интерфейса по мере появления необходимых ресурсов и поддержки платформы.