拿来就用!Vue3+Cesium 飞入效果封装,3D大屏多场景直接复用

最近有点儿事儿,之前的大屏项目拖了一段时间,现在打算继续开发。原本以为用熟悉的Cesium能快速搞定,没想到还是踩了几个坑,整理出来和大家分享,避免后续有人走同样的弯路。

页面地球飞入效果采用 Cesium 进行开发,一来 Cesium 作为开源的3D地理信息可视化框架,API 封装完善,开发效率高。

二来个人长期使用该框架,对其核心逻辑比较熟悉,本以为能快速落地,实际开发中却遇到了加载、时机监听、多场景复用等多个问题,逐一排查解决后,才实现了流畅的飞入效果。

实现效果

Cesium 地球初始化完成后,自动触发指定地点的飞入动画,相机从初始视角平滑过渡到目标经纬度对应的视角,过程流畅无卡顿、无图层闪烁。

核心问题

简单的飞入效果之前都是使用的现成的方法,从零开始遇到点问题。

实际开发中需要兼顾加载性能、时机准确性、多场景复用等,具体问题如下:

  • Cesium 加载速度问题:在线地图服务加载延迟高,弱网环境下易报错,影响用户体验;
  • Cesium 加载完成时机的监听:若监听时机不准确,会导致飞入动画触发时,地图图层、影像未加载完成,出现"空地球"或"图层闪烁"问题;
  • 多地点复用问题:大屏项目中可能需要切换多个目标地点,需对飞入逻辑进行封装,实现灵活调用。

解决方案(附完整代码+细节说明)

加载速度优化:解决在线地图加载慢的问题

我最初采用的是 Cesium 官方的 Ion 在线地图服务,毕竟无需额外配置,直接调用即可,但实际测试后发现两个致命问题:

  1. 加载延迟高:官方服务器位于海外,国内网络环境下,地图影像加载速度极慢,甚至需要10秒以上才能完全渲染,远超用户6~8秒的等待极限;

  2. 稳定性差:弱网环境下,地图会直接加载失败,控制台报错"影像图层加载超时",导致页面无法正常展示。

在线地图服务受网络环境影响极大,若要实现生产环境的稳定运行,优先替换为本地地图服务(如天地图本地部署、GeoServer 发布的本地影像),从根源上解决加载慢、报错的问题。

由于手头暂无本地地图部署工具,本次开发暂用在线地图过渡,后续会替换为本地服务。

以下是优化后的初始化代码,增加了加载超时处理,提升弱网环境下的容错性:

js 复制代码
// 导入 Cesium 核心模块
import * as Cesium from 'cesium'
// 引入 Cesium 样式(必须,否则控件和地球样式异常)
import 'cesium/Build/Cesium/Widgets/widgets.css'

// 初始化 Cesium 地球(增加超时处理,优化加载体验)
const initCesium = async () => {
    try {
        // 配置 Cesium Token(可从 Cesium 官网免费申请,需注册账号)
        Cesium.Ion.defaultAccessToken = '你的官网Token';

        // 创建 Cesium 视图实例,精简界面控件,提升加载速度
        viewer.value = new Cesium.Viewer('cesiumContainer', {
            // 隐藏默认控件,适配大屏简洁风格
            timeline: false, // 时间轴控件
            animation: false, // 动画控件
            baseLayerPicker: false, // 底图切换控件
            geocoder: false, // 地理编码控件(搜索地点)
            homeButton: false, // 首页按钮
            infoBox: false, // 信息弹窗(点击要素时显示)
            sceneModePicker: false, // 场景模式切换(2D/3D/哥伦布视图)
            navigationHelpButton: false, // 导航帮助按钮
            // 性能优化配置
            scene3DOnly: true, // 仅开启3D模式,减少2D渲染开销
            requestRenderMode: true, // 开启请求渲染模式,降低CPU占用
            maximumRenderTimeChange: 1 / 60, // 控制渲染帧率,避免卡顿
            // 开启地形(如果不需要地形展示,可注释,进一步提升加载速度)
            // terrainProvider: Cesium.createWorldTerrain()
        });

        // 隐藏 Cesium 底部版权信息(可选,根据项目需求调整)
        viewer.value._cesiumWidget._creditContainer.style.display = 'none';

        // 等待 Cesium 完全加载完成(包括影像图层、场景渲染)
        await waitForCesiumFullyLoaded();
        
        // 触发 cesiumReady 事件,通知外部执行飞入等后续操作
        emit('cesiumReady', viewer.value);
    } catch (error) {
        console.error('Cesium 初始化失败:', error);
        // 加载失败提示,提升用户体验
        ElMessage.error('地球加载失败,请检查网络或刷新页面重试');
    }
}

加载时机监听

这是本次开发最容易踩坑的点,在创建 viewer 实例后,直接触发飞入动画,导致动画执行时,地图影像还未加载完成,出现"相机飞向空地球"的尴尬场景。

Cesium 初始化是异步过程,创建 viewer 实例只是第一步,后续还需要加载影像图层、渲染场景、初始化相机等操作,这些操作完成后,才能确保飞入动画的流畅性。

封装两个异步方法,分别监听场景渲染就绪影像图层加载完成,只有两个条件都满足,才触发后续的飞入操作,确保时机精准。

代码如下:

js 复制代码
/**
 * 等待 Cesium 完全加载完成(包括场景渲染和影像图层)
 * 核心逻辑:先确保场景渲染就绪,再等待影像图层加载完成,双重校验
 * @returns {Promise}
 */
const waitForCesiumFullyLoaded = () => {
    return new Promise((resolve) => {
        const checkSceneReady = () => {
            // 先检查 viewer 和 scene 是否存在(避免初始化未完成时调用)
            if (!viewer.value || !viewer.value.scene) {
                // 每50ms检查一次,避免频繁占用资源
                setTimeout(checkSceneReady, 50);
                return;
            }
            
            // 使用 postRender 事件,确保场景至少完成一帧渲染
            viewer.value.scene.postRender.addEventListener(() => {
                // 场景就绪后,再等待影像图层加载完成
                waitForImageryLoaded().then(resolve);
            }, viewer.value.scene);
        };
        checkSceneReady();
    });
}

/**
 * 等待影像图层加载完成(单独封装,便于后续扩展)
 * 核心逻辑:遍历所有影像图层,检查是否有正在加载的图块
 * @returns {Promise}
 */
const waitForImageryLoaded = () => {
    return new Promise((resolve) => {
        // 若 viewer 或 scene 不存在,直接resolve(容错处理)
        if (!viewer.value || !viewer.value.scene) {
            resolve();
            return;
        }

        const imageryLayers = viewer.value.imageryLayers;
        // 若没有影像图层,直接resolve
        if (!imageryLayers || imageryLayers.length === 0) {
            resolve();
            return;
        }

        // 循环检查所有影像图层是否加载完成
        const checkLoaded = () => {
            let allLoaded = true;
            
            for (let i = 0; i < imageryLayers.length; i++) {
                const layer = imageryLayers.get(i);
                if (layer && layer.imageryProvider) {
                    // 检查当前图层是否有正在加载的图块(_loading 为Cesium内部属性)
                    if (layer._loading) {
                        allLoaded = false;
                        break;
                    }
                }
            }

            if (allLoaded) {
                // 确保影像加载完成后,场景再渲染一帧,避免闪烁
                viewer.value.scene.postRender.addEventListener(() => {
                    resolve();
                }, viewer.value.scene);
            } else {
                // 每100ms检查一次,平衡性能和准确性
                setTimeout(checkLoaded, 100);
            }
        };

        checkLoaded();
    });
}

关键注意点:将两个方法拆分开写,是为了后续扩展------比如项目中需要添加3D模型、矢量数据加载。

可直接在 waitForCesiumFullyLoaded 方法中添加对应的等待逻辑,无需大幅修改代码,提升可维护性。

封装飞入方法

大屏项目中,往往需要切换多个目标地点(如从全国视角飞入各省、从省视角飞入各市),若每次切换都重复编写代码冗余。

因此,简单封装一个通用的飞入方法。

js 复制代码
/**
 * 控制 Cesium 相机飞往指定目标地点(通用封装,支持多场景复用)
 * @param {Object} options - 飞行配置项(必传参数标注,可选参数有默认值)
 * @param {Number} options.longitude - 目标经度(必传,如北京:116.4074)
 * @param {Number} options.latitude - 目标纬度(必传,如北京:39.9042)
 * @param {Number} options.height - 目标高度 (米,必传,根据场景调整,如大屏常用5000米)
 * @param {Number} [options.duration=3] - 飞行时长 (秒,可选,默认3秒,兼顾流畅度和效率)
 * @param {Number} [options.heading=0] - 相机朝向 (角度,可选,0 为正北,可根据需求调整)
 * @param {Number} [options.pitch=-60] - 俯仰角 (角度,可选,-90 为垂直向下,-60为常用视角)
 * @param {Function} [options.onComplete] - 飞行完成回调(可选,如飞行结束后加载区域数据)
 * @param {Function} [options.onCancel] - 飞行取消回调(可选,如用户手动中断飞行时的处理)
 */
const flyToLocation = async (options) => {
    // 校验 viewer 实例是否存在,避免报错
    if (!viewer.value) {
        console.warn('Viewer 实例不存在,无法执行飞行操作');
        ElMessage.warning('地球未加载完成,无法执行飞入操作');
        return;
    }

    // 解构配置项,设置默认值
    const {
        longitude,
        latitude,
        height,
        duration = 3,
        heading = 0,
        pitch = -60,
        onComplete,
        onCancel
    } = options

    // 由于 cesiumReady 触发时已确保影像加载完成,这里直接执行飞行
    viewer.value.camera.flyTo({
        // 将经纬度、高度转换为 Cesium 支持的笛卡尔坐标系
        destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
        // 相机朝向配置(heading:方位角,pitch:俯仰角,roll:翻滚角)
        orientation: {
            heading: Cesium.Math.toRadians(heading), // 角度转弧度(Cesium 内部使用弧度)
            pitch: Cesium.Math.toRadians(pitch),
            roll: 0.0 // 翻滚角,默认0,无需调整
        },
        duration: duration, // 飞行时长
        complete: () => {
            console.log('已飞到目标地点!');
            // 执行完成回调(若有)
            if (onComplete) onComplete();
        },
        cancel: () => {
            console.log('飞行被取消!');
            // 执行取消回调(若有)
            if (onCancel) onCancel();
        },
        canInterrupt: true // 允许用户手动中断飞行(如鼠标拖拽相机)
    })
}

注意 :项目使用 Vue3 + setup 语法,需通过 defineExposeflyToLocation 方法导出,外部组件才能调用。

总结

Cesium 作为成熟的3D地理可视化框架,本身的 API 封装已经非常完善,实现飞入效果的核心逻辑并不复杂。

但实际开发中,往往是细节问题导致踩坑,总结几点关键经验,供大家参考:

  1. 加载优化优先选本地地图:生产环境中,务必替换掉官方在线地图,改用本地部署的地图服务(天地图、高德地图本地切片等),彻底解决加载慢、报错的问题;

  2. 加载时机监听不能省:不要省略 waitForCesiumFullyLoaded 方法,否则会出现图层闪烁、空地球等问题,拆分方法便于后续扩展;

  3. 封装逻辑提升复用性:多地点切换场景,一定要封装通用的飞入方法,明确配置项的必传/可选,增加容错处理,减少代码冗余;

  4. 内存管理要注意:页面卸载时,务必销毁 Cesium 实例(包括 viewer事件监听等),避免内存泄漏,导致页面卡顿、崩溃,销毁代码示例如下:

js 复制代码
// 页面卸载时销毁 Cesium 实例(Vue3 onUnmounted 中调用)
onUnmounted(() => {
    if (viewer.value) {
        // 销毁 viewer 实例,释放内存
        viewer.value.destroy();
        viewer.value = null;
    }
});

最后,Cesium 的坑大多集中在"加载时机 "和"性能优化"上,只要理清初始化流程、做好细节校验,就能快速实现流畅的交互效果。

后续我会继续更新这个大屏项目中 Cesium 的其他坑点,欢迎大家留言交流,共勉!

相关推荐
天蓝色的鱼鱼2 小时前
都2026年了还不会Vite插件开发?手写一个版本管理插件,5分钟包会!
前端·vite
苏武难飞2 小时前
分享一个33号远征队的效果!
前端
鹏程十八少3 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
亿元程序员3 小时前
这款值68亿的游戏,你不实战一下吗?安排!
前端
摸鱼的春哥4 小时前
Agent教程15:认识LangChain(中),状态机思维
前端·javascript·后端
明月_清风4 小时前
告别遮挡:用 scroll-padding 实现优雅的锚点跳转
前端·javascript
明月_清风4 小时前
原生 JS 侧边栏缩放:从 DOM 监听到底层优化
前端·javascript
万少13 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站15 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端