Интеграция Mapbox GL JS в Svelte опирается на различие между императивной графической библиотекой и реактивной UI-моделью. Mapbox GL JS управляет WebGL-контекстом напрямую, создаёт слой рендеринга карты, обрабатывает события и состояние камеры. Svelte, напротив, компилирует реактивные зависимости в оптимизированный JavaScript без виртуального DOM.
Основная задача интеграции заключается в синхронизации жизненного цикла карты с жизненным циклом компонента и в аккуратном связывании реактивных переменных с императивным API карты.
В Svelte ключевым моментом является стадия монтирования компонента. Инициализация экземпляра карты выполняется после того, как DOM-узел уже существует.
<script>
import { onMount, onDestroy } from "svelte";
import mapboxgl from "mapbox-gl";
let mapContainer;
let map;
</script>
<div bind:this={mapContainer} class="map"></div>
Инициализация экземпляра Mapbox GL JS происходит в
onMount:
onMount(() => {
map = new mapboxgl.Map({
container: mapContainer,
style: "mapbox://styles/mapbox/streets-v12",
center: [0, 0],
zoom: 2
});
});
Такой подход гарантирует наличие DOM-элемента контейнера и корректное создание WebGL контекста.
Mapbox GL JS создаёт WebGL контекст и подписки на события, поэтому требуется явное освобождение ресурсов при уничтожении компонента.
onDestroy(() => {
if (map) {
map.remove();
map = null;
}
});
Удаление экземпляра карты предотвращает утечки памяти и освобождает GPU-ресурсы.
Svelte предоставляет реактивные блоки $: для
синхронизации состояния. Это используется для обновления карты при
изменении переменных.
<script>
let lng = 0;
let lat = 0;
let zoom = 2;
$: if (map) {
map.setCenter([lng, lat]);
}
</script>
Изменение lng или lat автоматически
приводит к обновлению положения карты.
$: if (map) {
map.setZoom(zoom);
}
Такая реактивная связь упрощает интеграцию состояния интерфейса с камерой карты без необходимости вручную отслеживать события изменения.
Mapbox GL JS предоставляет богатую систему событий: перемещение карты, клики, загрузка стиля, взаимодействие с слоями.
onMount(() => {
map = new mapboxgl.Map({
container: mapContainer,
style: "mapbox://styles/mapbox/light-v11",
center: [30, 50],
zoom: 3
});
map.on("move", () => {
const center = map.getCenter();
lng = center.lng;
lat = center.lat;
});
});
Таким образом достигается двусторонняя синхронизация: изменения состояния обновляют карту, а действия пользователя обновляют состояние.
Mapbox GL JS поддерживает динамическое переключение стилей карты.
let style = "mapbox://styles/mapbox/streets-v12";
$: if (map && style) {
map.setStyle(style);
}
Изменение переменной style приводит к полной
перезагрузке визуального представления карты.
При смене стиля важно учитывать момент завершения загрузки:
map.on("style.load", () => {
// повторное добавление слоёв и источников
});
Маркер представляет собой отдельный DOM-объект, управляемый Mapbox GL JS.
let marker;
onMount(() => {
map = new mapboxgl.Map({
container: mapContainer,
style: "mapbox://styles/mapbox/outdoors-v12",
center: [10, 10],
zoom: 4
});
marker = new mapboxgl.Marker()
.setLngLat([10, 10])
.addTo(map);
});
Реактивное обновление позиции маркера:
$: if (marker) {
marker.setLngLat([lng, lat]);
}
Mapbox GL JS оперирует источниками (sources) и слоями (layers). Добавление GeoJSON источника:
onMount(() => {
map.on("load", () => {
map.addSource("points", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [20, 20]
}
}
]
}
});
map.addLayer({
id: "points-layer",
type: "circle",
source: "points",
paint: {
"circle-radius": 6,
"circle-color": "#ff0000"
}
});
});
});
Обновление данных источника при изменении реактивной переменной:
$: if (map && map.getSource("points")) {
map.getSource("points").setData(geojsonData);
}
Для масштабных приложений используется разделение состояния через store-модель.
// mapStore.js
import { writable } from "svelte/store";
export const coordinates = writable({ lng: 0, lat: 0 });
Подписка внутри компонента карты:
import { coordinates } from "./mapStore";
coordinates.subscribe(value => {
if (map) {
map.setCenter([value.lng, value.lat]);
}
});
Обратная синхронизация:
map.on("move", () => {
const center = map.getCenter();
coordinates.set({
lng: center.lng,
lat: center.lat
});
});
Svelte может использовать серверный рендеринг, однако Mapbox GL JS зависит от WebGL и DOM, что исключает выполнение на сервере.
Поэтому инициализация выполняется исключительно в клиентской среде:
import { browser } from "$app/environment";
onMount(() => {
if (!browser) return;
map = new mapboxgl.Map({
container: mapContainer,
style: "mapbox://styles/mapbox/dark-v11"
});
});
Интерактивные слои позволяют реагировать на события наведения и клика.
map.on("click", "points-layer", (e) => {
const feature = e.features[0];
console.log(feature.geometry.coordinates);
});
Подсветка при наведении:
map.on("mouseenter", "points-layer", () => {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "points-layer", () => {
map.getCanvas().style.cursor = "";
});
В архитектуре Svelte карта часто выделяется в отдельный компонент, а слои, маркеры и источники управляются через props и stores. Такой подход разделяет:
Mapbox GL JS загружает стили и тайлы асинхронно. Для синхронизации
состояния используется событие load:
map.on("load", () => {
mapLoaded = true;
});
Флаг загрузки позволяет безопасно выполнять операции добавления слоёв и источников.
При частых реактивных обновлениях важно учитывать стоимость операций:
setCenter и setZoom вызывают перерасчёт
камерыsetData пересобирает sourcesetStyle полностью пересоздаёт стильДля оптимизации применяется группировка реактивных изменений и контроль частоты обновлений через промежуточные состояния.
Размер контейнера карты должен синхронизироваться с layout:
import { onMount } from "svelte";
onMount(() => {
const resizeObserver = new ResizeObserver(() => {
map?.resize();
});
resizeObserver.observe(mapContainer);
});
Это обеспечивает корректное перерасчёт WebGL viewport при изменении размеров интерфейса.