ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Vue3 + Vite + Cesium 三维GIS项目从零搭建与踩坑指南

Vue3 + Vite + Cesium 三维GIS项目从零搭建与踩坑指南 先说结论Vue3 Vite Cesium 这个组合现在几乎是前端做三维 GIS 项目的标配起点。Vite 的秒级热更新配合 Cesium 这种重资源库开发体验比老一代 Webpack 方案舒服太多Vue3 的组合式 API 又能把 Cesium 的地球实例、图层、事件处理封装得干干净净。这篇博文我就按自己的实操路径从零开始带你搭一个能跑起来的 Cesium 项目把环境配置、项目初始化、核心代码、踩坑记录一次性说清楚适合刚接触三维 GIS 的前端开发者也适合准备在团队内搭建统一三维项目模板的同学参考。1. 整体设计与方案选型为什么非要用这套组合1.1 这套组合解决的核心问题很多人在群里问“vue3 后台管理系统怎么选型”“Vue3 Vite 能不能上生产”如果只是做普通的管理端Vue3 Vite 确实足够但如果项目里要放一张“地球”事情就不一样了。Cesium 是一个体量很大的三维地球引擎它自身带有大量静态资源纹理、shader、worker 脚本对构建工具的静态资源处理、代码压缩、开发服务器代理都有特殊要求。用 Vite 而不是 Webpack核心原因是开发阶段的启动速度和热更新效率。Cesium 相关项目动辄几十上百兆的源码和资源Webpack 冷启动要等十几秒甚至几十秒改一行代码可能要等 3 到 5 秒编译Vite 基于原生 ES Module启动几乎不用等改代码也是毫秒级热更新。我们团队之前有一个 Cesium 项目就是用 Webpack 搭的后来迁到 Vite同事反馈“终于有勇气改代码了”——这并不夸张。而选 Vue3 而不是 Vue2一方面是因为 Vue3 的组合式 API 在封装 Cesium 的 viewer、相机、实体时逻辑更集中一个useCesium组合函数就能完成实例化、销毁、事件注册代码可读性比 Options API 强不少另一方面 Vue3 的响应式系统在处理 Cesium 这类非响应式第三方库时边界更清晰不会像 Vue2 那样出现各种奇怪的响应式劫持问题。1.2 和微前端方案的关系热词里出现“vue3 vite 微前端方案”说明大家确实在考虑团队协作和模块拆分。Cesium 项目做成微前端时子应用的构建和资源加载是最大的坑因为 Cesium 的 worker 脚本和静态资源路径是内部动态计算的如果子应用被挂到某个嵌套路由下资源路径很容易 404。目前比较稳的做法是主应用用 Vite子应用也尽量用 Vite这样两个应用之间的依赖共享和资源路径策略能保持一致。Cesium 放在子应用里时需要把build.assetsDir配置为相对路径或者干脆把 Cesium 相关的 worker、静态资源单独放到 CDN在子应用里通过外部引入的方式加载。这个话题展开非常多这篇博文先聚焦单应用的初始化微前端方案以后我单独写一篇。1.3 适用场景与能力边界用这个组合能做什么最典型的是智慧城市、数字孪生、气象可视化、交通仿真、管线管理这种需要“一个大场景”打底的应用。Cesium 负责底图和场景渲染Vue3 负责业务界面和数据交互Vite 负责开发和构建优化。但要注意Cesium 不是建模工具也不是数据可视化图表库。你可以在上面加载 3D Tiles 建筑模型、地形、影像、矢量数据做视角飞行、动态光照、雷达扫描等效果但不能把业务数据表直接扔给 Cesium 渲染中间需要经过坐标转换、entity 或 primitive 的数据组织等处理。简单说Cesium 是“渲染引擎”Vue3 是“业务骨架”Vite 是“构建流水线”三者各管一段配合关系要搞清楚。2. 动手前的基础环境准备2.1 Node 版本选择这一步看似基础但坑非常多。Cesium 新版1.100 以上对 Node 版本有明确要求Vite 5 以上也要求 Node 18 以上如果你还在用 Node 14 或 16安装依赖时大概率会遇到ERR_OSSL_EVP_UNSUPPORTED之类的报错这是因为 Node 版本太旧OpenSSL 算法不兼容。我的建议是直接用 Node 18 LTS 或 Node 20 LTS。如果你电脑上装了多个 Node 版本推荐用 nvm 管理避免项目之间相互污染。检查方法很简单node -v npm -v如果 node 版本过低先升级 nvm# 安装指定版本 node比如 18.20.4 nvm install 18.20.4 nvm use 18.20.4之前帮一个同事排查问题他项目一直报digital envelope routines::unsupported查了半天发现是 Node 17 导致的切到 Node 18 之后一切正常。所以版本这关必须过别嫌啰嗦。2.2 npm 镜像源切换国内开发者安装 Cesium 时经常遇到npm install卡住或慢到离谱的情况因为 Cesium 的包里带了不少附带的二进制资源默认的 npm 官方源访问不稳定。建议先切换为淘宝镜像npm config set registry https://registry.npmmirror.com这里要注意镜像源对 Cesium 这种纯 JavaScript 包是没问题的但如果你后期要装一些带原生模块的包比如离线地形切片工具、GIS 相关的地理计算库可能还是需要从官方源拉取切换源之后万一装不上记得临时用npm install --registryhttps://registry.npmjs.org回退。2.3 用脚手架还是手动配置现在创建 Vue3 Vite 项目业界标准做法就是用官方脚手架create-vue或create-vite没有理由手写配置。create-vue是用npm create vuelatest调用的比create-vite多了一些可选插件比如 Vue Router、Pinia、ESLint更适合正式项目。如果你想要一个干净的环境来试验 Cesium直接用create-vite就够用了npm create vitelatest my-cesium-app -- --template vue注意这条命令在当前 npm 版本下会创建一个名为my-cesium-app的目录模板使用的是 Vue 3。3. 创建项目并接入 Cesium核心步骤拆解3.1 项目初始化先走一遍创建流程。进入你的工作目录执行npm create vitelatest my-cesium-app -- --template vue cd my-cesium-app npm install装完基础依赖后先看一眼目录结构确认package.json里 vue 和 vite 的版本。我创建时的版本大致是 Vue 3.4.x、Vite 5.x这个组合当前很稳定。然后顺手把src目录里没用的HelloWorld.vue和组件引用删掉保持App.vue干净。3.2 安装 Cesium安装 Cesium 是整篇博文的关键节点命令是npm install cesium默认安装的是最新版本当前最新版本已经到了 1.11x 左右。安装完成后node_modules/cesium目录里会有Build/Cesium/Cesium.js、Build/Cesium/Widgets/widgets.css等核心文件。这里有一个很多人都会踩的坑Cesium 的 npm 包自带的是 ES Module 版本在cesium/engine目录下和传统的window.Cesium全局变量用法不完全一样。Vite 项目里推荐用 ES Module 方式引入这也意味着你在代码里需要明确import * as Cesium from cesium; import cesium/Build/Cesium/Widgets/widgets.css;而不是直接引用Build/Cesium/Cesium.js。这个区别我在 3.4 节会进一步演示。3.3 vite.config.js 的专项配置这是整个初始化过程里最需要理解的一部分。先看代码import { defineConfig } from vite; import vue from vitejs/plugin-vue; import path from path; export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src), cesium: path.resolve(__dirname, node_modules/cesium), }, }, define: { process.env: {}, }, build: { chunkSizeWarningLimit: 4096, rollupOptions: { output: { manualChunks: { cesium: [cesium], }, }, }, }, server: { port: 3000, open: true, }, });逐条解释一下resolve.alias里的cesium别名是为了让import * as Cesium from cesium能更稳定地解析到正确的包路径。虽然 Vite 默认也能解析但加上别名可以防止某些情况下走到了 Cesium 包源码目录而不是构建后的版本。define里的process.env: {}是解决热词中“vite 中项目一直报错 process is not defined”的关键。Cesium 内部有一小部分代码使用了process.env来判断运行环境Vite 不像 Webpack 会自动注入 Node 的全局变量所以运行时会报process is not defined。加了这个配置Vite 会在代码里给process.env注入一个空对象Cesium 内部判断不会报错。build.chunkSizeWarningLimit和manualChunks是应对 Cesium 体积问题。Cesium 压缩后仍然有几 MB 的 JS单独拆成一个 chunk 并调大警告阈值这样vite build时不会被“块大小超过 500 kB”的警告刷屏同时浏览器也能单独缓存 Cesium 这个 chunk业务代码更新时 Cesium 不用重新下载。server配置主要是开发体验端口定成 3000open: true让启动后自动打开浏览器。3.4 在组件里初始化 Cesium 场景先写一个最简单的CesiumViewer.vue组件直接放在src/components目录下template div refcesiumContainer classcesium-container/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue; import * as Cesium from cesium; import cesium/Build/Cesium/Widgets/widgets.css; const cesiumContainer ref(null); let viewer null; onMounted(() { if (!cesiumContainer.value) return; // 这里先不填 token默认的 Ion 资源会有一个提示弹窗 // 后面申请到 token 之后再替换。 viewer new Cesium.Viewer(cesiumContainer.value, { animation: false, baseLayerPicker: false, fullscreenButton: false, geocoder: false, homeButton: false, infoBox: false, sceneModePicker: false, selectionIndicator: false, timeline: false, navigationHelpButton: false, // 如果你不想看到默认的 Ion 数字地球可以关掉 imageryProvider: false, baseLayer: Cesium.ImageryLayer.fromProviderAsync( Cesium.OpenStreetMapImageryProvider.fromUrl( https://tile.openstreetmap.org/ ) ), }); // 添加一个简单的点和标签验证地球已经能交互 viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(116.39, 39.9), point: { pixelSize: 10, color: Cesium.Color.RED }, label: { text: 北京, font: 14px sans-serif, pixelOffset: new Cesium.Cartesian2(0, -20), }, }); }); onBeforeUnmount(() { if (viewer) { viewer.destroy(); viewer null; } }); /script style scoped .cesium-container { width: 100%; height: 100vh; } /style然后在App.vue里引入这个组件template CesiumViewer / /template script setup import CesiumViewer from ./components/CesiumViewer.vue; /script执行npm run dev浏览器打开http://localhost:3000你应该能看到一个带 OSM 底图的地球中心定位在中国区域北京位置有一个红色点和文字标签。说一下我为什么用OpenStreetMapImageryProvider.fromUrl而不是默认的 Cesium Ion因为 Ion 的底图需要注册 token而且多数时候默认底图在国内访问会有些慢OSM 底图相对简单适合作为开发环境的验证数据。后期接入业务时可以切换成自己的影像服务或者 Cesium Ion 服务。4. 核心细节解析与参数选择4.1 Viewer 构造参数到底该怎么调上面那份示例代码里我把一堆默认控件全部设为false这是刻意为之。Cesium.Viewer 默认会创建非常多的 UI 组件包括动画窗口、时间线、地理编码搜索框、航拍切换器、帮助按钮等。对于业务型项目这些界面通常不需要或者需要放到自定义的面板里所以关掉它们能让界面更干净。但有一个参数要特别提醒imageryProvider: false加baseLayer的组合方式在 Cesium 1.107 之后发生了变化。旧版本里可以直接传imageryProvider新版本更推荐使用baseLayerImageryLayer.fromProviderAsync的方式否则控制台会有弃用警告。我在 1.111 版本测试下来上面的写法是没问题的。如果你希望 Cesium 显示默认的卫星底图最简单的方式就是不要传imageryProvider: false直接new Cesium.Viewer(container)然后它会弹出一个提示框要求输入 Ion token。首次接入会把一个默认的全球 3D 地形和 Bing 影像加载出来。4.2 每个参数背后的意图很多人照着文档抄参数但不知道每个参数管什么等出了问题又无从下手。我把自己常用的配置参数整理成一个速查表参数作用建议animation左下角的动画播放控件非仿真需求建议关掉timeline底部时间轴默认打开非时间相关项目建议关掉geocoder搜索地名控件建议关掉需要时自己封装搜索接口homeButton回默认视角按钮根据需求保留sceneModePicker2D/3D 切换按钮如果业务不涉及 2D关掉baseLayerPicker底图切换按钮如果只提供单一底图关掉navigationHelpButton操作帮助按钮建议关掉fullscreenButton全屏按钮如果项目本身有全屏逻辑关掉infoBox点击实体时弹出信息框如果用自定义弹窗关掉selectionIndicator选中的绿色指示符如果用自定义选中效果关掉这里有一个容易忽略的点关掉infoBox和selectionIndicator之后Cesium 的默认点击拾取行为还在但是弹窗和指示符没了需要自己在鼠标事件里做业务处理。这是很多新手项目里“点了没反应”的原因——不是没拾取到而是 UI 被关掉了。4.3 家目录和默认视角设置Viewer构造之后通常会设置一个初始视角。Cesium 的默认视角是从太空中看地球离地面很远你需要在初始化后调用camera.setView或flyToviewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 15000), orientation: { heading: 0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0, }, });setView是瞬间定位flyTo是动画飞行。我建议初始化场景用setView用户点击某个菜单切换视角时用flyTo这样交互体验更自然。5. 常见问题与排查技巧实录5.1 process is not defined高频问题这是热词里被问得最多的问题。表现是控制台直接红屏报错Uncaught ReferenceError: process is not defined。原因是 Cesium 用了 Node 环境的全局变量而浏览器里没有。解决方案有三种第一种在vite.config.js里加definedefine: { process.env: {}, },第二种不配置 define但在入口文件顶部手动声明window.process { env: {} };第三种如果你在用其他依赖也依赖 process 变量可以使用vite-plugin-node-polyfills这种插件来做统一 polyfill但 Cesium 场景下一般用不到加个空对象就够了。我实际测试下来第一种方案最干净。要注意的是如果你以后在代码里也直接用了process.envVite 会把它替换成{}但process.env.NODE_ENV这类取值会变成undefined所以尽量别在业务代码里依赖它生产环境判断用 Vite 的import.meta.env.MODE代替。5.2 样式文件加载不出来如果页面出现地球浏览器窗口但是地球黑屏或按钮没有样式八成是widgets.css没引入。Cesium 的 UI 控件样式都在这个 CSS 里没有它地球虽然能创建出来但底图可能渲染异常控件布局混乱。在 Vue 组件里直接import cesium/Build/Cesium/Widgets/widgets.css即可。如果你用了按需加载组件的方式也要确保这个 CSS 被收集到构建产物里否则部署上去就会样式崩溃。5.3 Cesium Ion token 设置如果不使用默认的 OSM 底图而是想用 Cesium 官方提供的全球地形和 Bing 底图需要去 Cesium Ion 注册并申请 token。申请地址在官方控制台。拿到 token 之后在初始化之前设置Cesium.Ion.defaultAccessToken 你的token;或者在new Cesium.Viewer时传const viewer new Cesium.Viewer(cesiumContainer, { // 其他参数 });这里建议项目里把 token 放到.env文件中VITE_CESIUM_TOKENeyJhbGcixxx然后在代码里Cesium.Ion.defaultAccessToken import.meta.env.VITE_CESIUM_TOKEN;注意不要使用process.env.VITE_CESIUM_TOKENVite 环境下要用import.meta.env。5.4 打包体积与构建速度有热词提到“vite 打包太慢”这在引入 Cesium 之后尤其明显。Cesium 体积大压缩后也要 2 到 3 MB如果业务代码和 Cesium 混在一个 chunk 里每次更新业务代码都需要重新加载这么大体量的资源体验很糟糕。我在 3.3 节的配置里已经用了manualChunks把 Cesium 单独拆出来这样有两个好处一是业务代码和 Cesium 的缓存可以分开业务发布后老用户不会重新下载 Cesium二是构建时两个 chunk 可以并行优化。另外如果项目对首屏体积特别敏感还可以用 Cesium 的cesium/Build/Cesium/Cesium.js的非源码版本或者在vite.config.js里开启build.sourcemap: false默认也是 false。实际生产环境Cesium 的包体就让它那么大不要做过分压缩反而影响运行性能。5.5 底图加载失败或显示灰色如果配置了 OSM 方式但底图加载不出来优先检查网络。OSM 在国内部分地区访问不稳定这是客观情况。另一个可能原因是 Cesium 的OpenStreetMapImageryProvider请求的瓦片 URL 发生了变化可以更新 Cesium 版本或者换用ArcGisMapServerImageryProviderEsri 的全球影像const imageryProvider await Cesium.ArcGisMapServerImageryProvider.fromUrl( https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer );ImageryLayer.fromProviderAsync是异步创建 provider用async/await能确保在实例化ImageryLayer时 provider 已经准备完成。如果你用旧写法new Cesium.ArcGisMapServerImageryProvider(...)新版 Cesium 会直接报错或给出弃用提示务必注意。6. 从能跑到能用Entity 与 Primitive 的选型思路6.1 两者的定位差异热词里有“cesium 用 entity 跟 primitive 有什么区别”这个确实是刚上手 Cesium 时最纠结的问题。简单回答Entity 是高级封装适用于业务数据和场景标注Primitive 是底层渲染接口适用于需要高性能渲染大量几何体的场景。Entity 的底层实际也是转换为 primitive 渲染的但它替你管理了状态更新、拾取、样式同步等逻辑开发者只需要声明“有一个点、位置多少、颜色什么”即可。Cesium 会维护这个实体的生命周期你修改 position 或颜色画面会实时更新。而 Primitive 直接操作几何对象和材质性能更高但需要手动管理几何体、外观、更新逻辑。一个 Entity 在内部可能对应多个 primitive 或 draw command当你需要同时渲染数万甚至数十万个点时Entity 的封装开销就成问题了此时用 Primitive 或自定义 Geometry 更合适。6.2 选型实践建议我的个人经验是业务要求“可点选、可弹窗、可实时修改属性”时优先用 Entity几十个几百个实体完全没问题业务要求“全场景同时展示海量数据”比如一万个传感器点位必须用 Primitive如果既想要 Entity 的方便性又担心性能可以考虑用Cesium.PrimitiveCollection管理 Primitive然后自己写一个点击拾取函数Cesium 的官方文档里Entity API 是推荐入口Primitive 是性能通道二者不是互斥关系在同一个 Viewer 里可以混用。6.3 一个 Entity 的完整示例给新手的建议先理解 Entity 的操作方式。下面这个示例创建了一个随时间移动的点const position Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0); const entity viewer.entities.add({ position: position, point: { pixelSize: 12, color: Cesium.Color.AQUA }, label: { text: 移动目标, font: 14px sans-serif, pixelOffset: new Cesium.Cartesian2(0, -20), }, }); // 修改属性 entity.position Cesium.Cartesian3.fromDegrees(116.41, 39.92, 0);Entity 的属性赋值是响应式的Cesium 内部监听 position 变化后会自动刷新位置这种思路和 Vue3 的响应式设计有异曲同工的感觉。理解这一点后面做雷达扫描、动态光照、移动目标跟踪类需求时会顺手很多。7. 开发工作流从单组件到完整业务项目7.1 封装 Cesium 相关代码在实际项目中我不会把 Cesium 逻辑全写在组件里而是拆成独立模块src/ ├── components/ │ └── CesiumViewer.vue ├── utils/ │ └── cesium/ │ ├── viewer.js // 初始化 viewer │ ├── layers.js // 加载影像、地形图层 │ └── entities.js // 添加业务实体 └── hooks/ └── useCesium.js // Vue 组合函数封装useCesium.js的核心思路是暴露一个viewer的 ref并且提供初始化和销毁的方法import { ref, onMounted, onBeforeUnmount } from vue; export function useCesium(containerRef, options {}) { const viewer ref(null); let cesiumViewer null; const initViewer () { if (cesiumViewer) return; cesiumViewer new Cesium.Viewer(containerRef.value, options); viewer.value cesiumViewer; }; const destroyViewer () { if (cesiumViewer) { cesiumViewer.destroy(); cesiumViewer null; viewer.value null; } }; onMounted(initViewer); onBeforeUnmount(destroyViewer); return { viewer, }; }在组件里使用script setup import { ref } from vue; import { useCesium } from ../hooks/useCesium; const container ref(null); const { viewer } useCesium(container, { // 配置... }); /script7.2 对接后端数据很多刚入门的人问“vue3 怎么连接后端”这个和普通前端项目没什么区别用 axios 或 fetch 拉数据然后把坐标数据转成 Entity 或 Primitive 渲染即可。唯一要注意的是坐标系的转换。后端给的经纬度一般是 EPSG:4326WGS84Cesium 内部用笛卡尔坐标EPSG:4978 的近似。创建点时用Cesium.Cartesian3.fromDegrees(lng, lat, height)即可。如果是行业项目数据可能是 CGCS2000 或 GCJ02需要先做投影转换这里先不展开。7.3 生产部署注意事项最后提一个线上部署的大坑。Vite 默认的base是/如果你把 Cesium 项目部署到服务器子目录比如https://xxx.com/gis/会导致资源路径全部 404。解决方式是设置相对路径// vite.config.js export default defineConfig({ base: ./, });设置后构建产物里的静态资源引用会变成相对路径Cesium 的 worker 和资源加载也能正常工作。这个坑我踩过一次当时整个团队盯着白屏页面查了很久最后发现只是路径前缀的问题。最后再说点实在话初始化一个 Cesium 项目本质上不难难的是对“三维渲染引擎 业务框架 构建工具”三者关系的理解。我在实际开发中最大的体会是不要把 Cesium 当作一个 Vue 组件来用它是独立于 Vue 之外的一个运行时实例Vue 只负责承载它的容器和业务面板二者通过组合式 API 做松耦合才是正路。上面给的配置和代码都是我在版本迭代中反复踩坑后沉淀下来的直接抄作业基本能跑通。跑通之后下一步可以试着加载 3D Tiles 瓦片集、接入地形服务、封装飞行漫游动画那才是 Cesium 真正发挥威力的场景以后有机会再写。
返回列表