В Mapbox GL JS размер карты напрямую зависит от размеров DOM-контейнера, в который она инициализирована. Библиотека не управляет версткой самостоятельно — она лишь считывает вычисленные CSS-значения ширины и высоты и использует их для расчета viewport WebGL-контекста.
Ключевая особенность заключается в том, что изменение размеров контейнера не всегда автоматически синхронизируется с рендерингом карты. Это приводит к необходимости явного уведомления карты о том, что геометрия viewport изменилась.
Основной метод для этого:
map.resize();
Метод пересчитывает размеры canvas и корректирует внутренние матрицы проекции.
Правильная адаптация начинается на уровне CSS. Контейнер должен иметь предсказуемую модель размеров, иначе карта будет рассчитывать некорректный viewport.
Типичная структура:
<div id="map"></div>
html, body {
margin: 0;
height: 100%;
}
#map {
width: 100%;
height: 100vh;
}
Здесь ключевым является использование vh или процентных
значений при условии, что родительские элементы имеют заданную
высоту.
Инициализация:
mapboxgl.accessToken = 'TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6173, 55.7558],
zoom: 10
});
При изменении размера окна браузера контейнер карты меняет геометрию, но WebGL canvas остаётся прежнего размера до явного обновления.
Стандартный подход:
window.addEventListener('resize', () => {
map.resize();
});
Однако этот вариант не учитывает случаи, когда изменение размеров происходит не из-за окна, а из-за layout-изменений внутри приложения.
В SPA и интерфейсах с панелями часто встречается ситуация, когда
карта находится внутри flex или grid layout, где размеры могут
изменяться без события resize окна.
Пример layout:
.wrapper {
display: flex;
height: 100vh;
}
.sidebar {
width: 300px;
}
.map-container {
flex: 1;
position: relative;
}
#map {
width: 100%;
height: 100%;
}
Если sidebar изменяет ширину (например, скрывается), контейнер карты изменяется, но событие window resize не срабатывает.
Современный способ отслеживания изменений размеров контейнера —
ResizeObserver.
const container = document.getElementById('map');
const observer = new ResizeObserver(() => {
map.resize();
});
observer.observe(container);
Этот подход реагирует на любые изменения размеров:
При некоторых изменениях DOM браузер сначала перерасчитывает layout,
а затем применяет стили. В таких случаях вызов map.resize()
может произойти до завершения перерасчета.
Решение — отложенный вызов:
const resizeMap = () => {
requestAnimationFrame(() => {
map.resize();
});
};
Или двойной RAF для сложных интерфейсов:
const resizeMap = () => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
map.resize();
});
});
};
Особенно критичный случай — карта внутри элемента с
display: none.
Mapbox GL JS не может вычислить размеры скрытого контейнера, поэтому после его отображения необходимо принудительно пересчитать размеры.
sidebar.classList.add('hidden');
// позже
sidebar.classList.remove('hidden');
map.resize();
Если этого не сделать, карта может остаться с нулевыми или устаревшими размерами canvas.
В интерфейсах с вкладками карта часто инициализируется в неактивной вкладке, где контейнер имеет нулевые размеры. Это приводит к тому, что WebGL canvas создается с нулевой геометрией.
Корректная стратегия:
tabButtons.forEach(btn => {
btn.addEventListener('click', () => {
showTab('mapTab');
map.resize();
});
});
При переходе в fullscreen изменяется размер контейнера и пересчитываются системные параметры viewport.
document.addEventListener('fullscreenchange', () => {
map.resize();
});
Это особенно важно, так как fullscreen меняет не только CSS-геометрию, но и внутренние размеры device pixel ratio.
Mapbox GL JS автоматически учитывает devicePixelRatio,
но изменение масштаба системы (например, drag окна между мониторами с
разной плотностью пикселей) может требовать пересчета.
window.addEventListener('devicepixelratiochange', () => {
map.resize();
});
Хотя событие не является стандартным во всех браузерах, логика адаптации остаётся актуальной в кастомных окружениях.
Частые вызовы map.resize() могут приводить к лишним
перерасчетам WebGL сцены. При сложных интерфейсах важно агрегировать
изменения.
Пример батчинга:
let resizeScheduled = false;
function scheduleResize() {
if (resizeScheduled) return;
resizeScheduled = true;
requestAnimationFrame(() => {
map.resize();
resizeScheduled = false;
});
}
Этот подход особенно полезен при массовых DOM-изменениях.
Если контейнер изменяет размер плавно (например, анимация sidebar), требуется учитывать промежуточные состояния.
.sidebar {
transition: width 0.3s ease;
}
В этом случае ResizeObserver будет вызывать
map.resize() многократно во время анимации. Это может быть
нежелательно.
Оптимизация — debounce:
let timeout;
const observer = new ResizeObserver(() => {
clearTimeout(timeout);
timeout = setTimeout(() => {
map.resize();
}, 50);
});
Если карта используется как фон слоя интерфейса:
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
Ключевым становится контроль высоты через top/bottom, а
не height. Это позволяет карте автоматически подстраиваться
под родительский контейнер без дополнительных вычислений.
В многоуровневых интерфейсах (панели, модальные окна, split-view) карта часто оказывается в цепочке контейнеров, каждый из которых может влиять на итоговый размер.
Типичная проблема — один из предков имеет
overflow: hidden и изменяемую высоту. В таких случаях
ResizeObserver должен наблюдать именно тот элемент, который
фактически определяет viewport карты, а не внешний wrapper.
В некоторых случаях требуется синхронизировать размер не только canvas, но и логическое состояние карты (например, после восстановления layout).
function syncMapLayout() {
map.resize();
map.triggerRepaint();
}
triggerRepaint() используется для принудительного
перерисовывания сцены после изменения геометрии, если изменения
происходят в нестандартных циклах рендеринга.
Если карта создается внутри скрытого контейнера, WebGL контекст может быть инициализирован с нулевыми размерами. Это приводит к некорректной отрисовке тайлов и слоев.
Стратегия корректной инициализации:
map.resize() после показаconst map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/light-v11' });
showContainer();
map.resize();
map.resize()ResizeObserver как
базовый механизм отслеживания