Интеграция CesiumJS в Vue строится вокруг прямого управления DOM-элементом, в который монтируется WebGL-контекст. Основная идея заключается в том, что Vue отвечает за жизненный цикл компонента, а Cesium — за рендеринг сцены и управление 3D-графикой.
Типовая структура компонента сводится к созданию контейнера и
инициализации Viewer после монтирования:
<template>
<div ref="cesiumContainer" class="cesium-container"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from "vue";
import * as Cesium from "cesium";
const cesiumContainer = ref(null);
let viewer = null;
onMounted(() => {
viewer = new Cesium.Viewer(cesiumContainer.value, {
terrainProvider: Cesium.createWorldTerrain(),
animation: false,
timeline: false,
});
});
onBeforeUnmount(() => {
if (viewer && !viewer.isDestroyed()) {
viewer.destroy();
viewer = null;
}
});
</script>
<style>
.cesium-container {
width: 100%;
height: 100vh;
}
</style>
Ключевым моментом выступает строгая привязка инициализации к
onMounted, поскольку до этого момента DOM-узел недоступен
для WebGL-контекста.
Cesium использует императивную модель, тогда как Vue — реактивную. Прямое связывание объектов Cesium с реактивными состояниями приводит к избыточным пересозданиям объектов сцены и снижению производительности.
Корректная модель взаимодействия строится через явные эффекты обновления:
import { watch } from "vue";
const zoomLevel = ref(1.0);
watch(zoomLevel, (value) => {
if (viewer) {
viewer.camera.zoomIn(value);
}
});
Объекты Cesium не должны попадать в reactive() или
ref(), так как они содержат сложные структуры WebGL и
внутренние ссылки, несовместимые с проксированием Vue.
При построении сложных приложений Cesium логика разделяется на изолированные компоненты: карта, слои, сущности, управление камерой.
Пример выделения слоя в отдельную композиционную функцию:
// useCesiumEntities.js
import * as Cesium from "cesium";
export function useCesiumEntities(viewer) {
const addPoint = (lon, lat) => {
return viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat),
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW,
},
});
};
const clear = () => {
viewer.entities.removeAll();
};
return {
addPoint,
clear,
};
}
Использование внутри компонента:
const { addPoint } = useCesiumEntities(viewer);
addPoint(71.4304, 51.1284);
Такой подход уменьшает связность между UI-слоем Vue и графическим ядром Cesium.
Cesium обладает значительным размером, поэтому динамический импорт снижает нагрузку на старт приложения.
onMounted(async () => {
const Cesium = await import("cesium");
viewer = new Cesium.Viewer(cesiumContainer.value, {
terrainProvider: Cesium.createWorldTerrain(),
});
});
При использовании Vite или Webpack важно учитывать корректную обработку статических ресурсов Cesium (Workers, Assets, Widgets).
Для Vite часто требуется настройка:
import { defineConfig } from "vite";
import cesium from "vite-plugin-cesium";
export default defineConfig({
plugins: [cesium()],
});
Cesium использует Web Workers для рендеринга и обработки тайлов. Некорректная настройка путей приводит к ошибкам загрузки.
Базовая конфигурация включает определение базового URL:
window.CESIUM_BASE_URL = "/cesium";
Статические ресурсы должны быть доступны в публичной директории приложения. Это включает:
Камера Cesium является центральным объектом управления сценой. Интеграция через Vue обычно строится через отдельные composables.
export function useCamera(viewer) {
const flyTo = (lon, lat, height = 1000) => {
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, height),
});
};
const setTopDownView = () => {
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(0, 0, 20000000),
orientation: {
heading: 0,
pitch: -Math.PI / 2,
roll: 0,
},
});
};
return {
flyTo,
setTopDownView,
};
}
Cesium генерирует низкоуровневые события, которые необходимо аккуратно адаптировать к Vue-архитектуре.
onMounted(() => {
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position);
if (picked) {
console.log("Selected object:", picked);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
});
При разрушении компонента обработчики должны очищаться вручную:
onBeforeUnmount(() => {
handler?.destroy();
});
Cesium активно использует 3D Tiles для потоковой загрузки больших наборов данных.
const tileset = new Cesium.Cesium3DTileset({
url: "https://assets.cesium.com/tileset.json",
});
viewer.scene.primitives.add(tileset);
tileset.readyPromise.then(() => {
viewer.zoomTo(tileset);
});
В Vue-архитектуре такие объекты не должны попадать в реактивное состояние, так как их обновление управляется исключительно Cesium.
Изменение размеров компонента требует синхронизации с WebGL canvas.
import { onMounted } from "vue";
onMounted(() => {
const resizeObserver = new ResizeObserver(() => {
if (viewer) {
viewer.resize();
}
});
resizeObserver.observe(cesiumContainer.value);
});
Без этого механизма сцена теряет корректное соотношение сторон при изменении layout.
В сложных приложениях Cesium Viewer передаётся через dependency injection, что позволяет избегать prop drilling.
// provider
import { provide } from "vue";
provide("cesiumViewer", viewer);
// consumer
import { inject } from "vue";
const viewer = inject("cesiumViewer");
Данный механизм особенно эффективен при наличии нескольких слоёв интерфейса, взаимодействующих с одной сценой.
При использовании <KeepAlive> Vue компоненты не
уничтожаются полностью, что требует отдельного управления ресурсами
Cesium.
import { onActivated, onDeactivated } from "vue";
onDeactivated(() => {
viewer?.scene.requestRender();
});
onActivated(() => {
viewer?.resize();
});
Cesium продолжает удерживать WebGL-контекст, поэтому контроль активности сцены становится критичным для предотвращения утечек памяти.
Cesium часто используется как визуализация слоя поверх глобального состояния (Pinia или Vuex). При этом состояние хранит только сериализуемые данные, а Cesium служит их визуальным представлением.
Пример синхронизации:
watch(
() => store.points,
(points) => {
viewer.entities.removeAll();
points.forEach((p) => {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat),
point: { pixelSize: 8 },
});
});
},
{ deep: true }
);
Такой подход обеспечивает разделение ответственности: состояние управляет данными, Cesium — визуализацией.
Cesium активно использует GPU-ресурсы, поэтому корректное уничтожение объектов критично.
onBeforeUnmount(() => {
if (viewer && !viewer.isDestroyed()) {
viewer.entities.removeAll();
viewer.destroy();
viewer = null;
}
});
Игнорирование очистки приводит к накоплению WebGL-контекстов, что особенно заметно при частой навигации между страницами Vue Router.
Разделение Cesium на модули позволяет уменьшить начальный размер бандла:
const Cesium = await import("cesium/Source/Cesium.js");
import "cesium/Source/Widgets/widgets.css";
Такой подход требует ручной настройки сборщика, но даёт контроль над тем, какие части библиотеки загружаются в конкретный момент.
При смене маршрута часто требуется переключение контекста сцены или её пересоздание.
import { watch } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
watch(
() => route.name,
() => {
viewer?.camera.flyHome(1);
}
);
В более сложных сценариях применяется полное уничтожение и повторная инициализация Viewer для каждого маршрута.