Интеграция Mapbox GL JS с веб-приложением начинается с создания DOM-элемента, который будет служить контейнером для WebGL-карты. Этот контейнер должен иметь явно заданные размеры, иначе рендеринг карты не произойдёт корректно.
<div id="map"></div>
#map {
width: 100%;
height: 100vh;
}
Ключевым условием работы является подключение библиотеки и указание access token, выдаваемого платформой Mapbox.
import mapboxgl from 'mapbox-gl';
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
Инициализация карты выполняется через конструктор Map,
которому передаётся конфигурационный объект:
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [37.6173, 55.7558],
zoom: 10
});
Параметр container принимает либо строковый
идентификатор элемента, либо сам DOM-узел. При интеграции в
SPA-фреймворки предпочтительнее использовать прямую ссылку на элемент,
чтобы исключить проблемы повторного рендера.
const container = document.getElementById('map');
const map = new mapboxgl.Map({
container,
style: 'mapbox://styles/mapbox/light-v11'
});
Особенность WebGL-рендеринга заключается в том, что карта создаёт собственный canvas-слой внутри контейнера, полностью контролируя отрисовку.
Инициализация карты не означает её готовность к работе. Основная
конфигурация и тайлы загружаются асинхронно. Для корректного добавления
слоёв и источников используется событие load.
map.on('load', () => {
map.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
});
До наступления события load любые операции над слоями
приводят к ошибкам, так как стиль карты ещё не полностью
инициализирован.
Карта предоставляет набор событий, отражающих состояние рендеринга:
load — завершена загрузка стиляrender — происходит каждый кадр отрисовкиidle — нет активных обновленийremove — карта уничтоженаИспользование idle полезно для выполнения операций после
полной стабилизации карты:
map.on('idle', () => {
console.log('Карта полностью загружена и стабилизирована');
});
Интеграция карты в приложение требует синхронизации с состоянием UI: фильтры, маршруты, выбор объектов. Карта выступает как отдельный рендер-движок, не связанный с DOM-состоянием напрямую.
function updateFilter(category) {
map.setFilter('points-layer', [
'==',
['get', 'category'],
category
]);
}
В этом подходе карта получает только декларативное описание фильтрации, без необходимости перерисовки всего источника данных.
Камера карты представляет собой абстракцию, включающую центр,
масштаб, наклон и поворот. Управление осуществляется через методы
flyTo, easeTo, jumpTo.
map.flyTo({
center: [30.3158, 59.9391],
zoom: 12,
speed: 1.2,
curve: 1.4
});
document.getElementById('btn-spb').addEventListener('click', () => {
map.easeTo({
center: [30.3158, 59.9391],
zoom: 11
});
});
Ключевая особенность интеграции — управление картой через внешние события без прямого вмешательства в её внутренний рендер.
Слои являются основным механизмом визуализации данных. Каждый слой
привязан к источнику данных (source) и описывает способ
отображения.
map.on('load', () => {
map.addSource('cities', {
type: 'geojson',
data: '/data/cities.geojson'
});
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-radius': 6,
'circle-color': '#ff5500'
}
});
});
map.getSource('cities').setData(newGeoJsonData);
Этот механизм позволяет интегрировать карту с потоковыми данными, REST API и WebSocket-каналами.
Карты часто используются как визуализация динамических данных: транспорт, IoT, геотрекинг. В таких сценариях важно минимизировать перерисовки.
const socket = new WebSocket('wss://example.com/geo');
socket.onmess age = (event) => {
const data = JSON.parse(event.data);
map.getSource('vehicles').setData(data);
};
Оптимизация достигается за счёт обновления только источника данных, а не пересоздания слоёв.
При использовании React, Vue или аналогичных фреймворков карта должна инициализироваться строго после монтирования DOM-узла.
let mapInstance;
function initMap(container) {
mapInstance = new mapboxgl.Map({
container,
style: 'mapbox://styles/mapbox/dark-v11'
});
}
Важно предотвращать повторную инициализацию при повторных рендерах компонента.
function destroyMap() {
if (mapInstance) {
mapInstance.remove();
mapInstance = null;
}
}
Метод remove() освобождает WebGL-контекст и уничтожает
внутренние обработчики событий.
Карта не отслеживает изменения контейнера автоматически. При
изменении размеров окна необходимо явно вызывать
resize.
window.addEventListener('resize', () => {
map.resize();
});
При использовании flex- или grid-лейаутов это особенно важно, поскольку контейнер может менять размеры без перезагрузки страницы.
Интеграция часто включает наложение UI поверх карты: панели, тултипы, списки объектов.
.map-ui {
position: absolute;
top: 10px;
left: 10px;
z-index: 1;
}
Карта всегда остаётся в нижнем слое, так как canvas имеет базовый контекст рендеринга.
В некоторых сценариях требуется отображение нескольких карт одновременно, например сравнение регионов.
const mapA = new mapboxgl.Map({
container: 'mapA',
style: 'mapbox://styles/mapbox/streets-v12'
});
const mapB = new mapboxgl.Map({
container: 'mapB',
style: 'mapbox://styles/mapbox/satellite-v9'
});
Синхронизация камеры осуществляется вручную через обработчики событий:
mapA.on('move', () => {
const center = mapA.getCenter();
mapB.setCenter(center);
});
Переключение стиля влияет на все слои и источники, поэтому необходимо учитывать пересоздание состояния.
map.setStyle('mapbox://styles/mapbox/outdoors-v12');
map.once('style.load', () => {
map.addSource(...);
map.addLayer(...);
});
Любая интеграция должна учитывать, что смена стиля полностью сбрасывает визуальную конфигурацию карты.
Интерактивность реализуется через события карты и слоёв.
map.on('click', 'cities-layer', (e) => {
const feature = e.features[0];
new mapboxgl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(`<strong>${feature.properties.name}</strong>`)
.addTo(map);
});
Слои становятся интерактивными только при явном указании параметра
interactive через обработчики событий.
Карта использует WebGL, поэтому основная нагрузка ложится на GPU. Однако интеграция с приложением может создавать узкие места при частых обновлениях данных.
Рекомендованные подходы:
source вместо пересоздания слоёвsetDatarequestAnimationFrame для синхронизации
обновленийlet pendingData = null;
function scheduleUpdate(data) {
pendingData = data;
requestAnimationFrame(() => {
if (pendingData) {
map.getSource('points').setData(pendingData);
pendingData = null;
}
});
}
Карта поддерживает кастомные контролы, которые интегрируются как DOM-элементы.
class CustomControl {
onAdd(map) {
this.container = document.createElement('div');
this.container.textContent = 'Control';
return this.container;
}
onRemove() {
this.container.remove();
}
}
map.addControl(new CustomControl(), 'top-right');
Контролы позволяют связывать карту с бизнес-логикой приложения без нарушения архитектуры рендеринга.