UniApp 引入 Cesium 开发: RenderJS 避坑

在 UniApp 环境下,Cesium 属于高频操作 DOM 和大量计算的库,而 App 端逻辑层(JSCore/V8)与视图层(WebView)是分离的。为了实现高性能渲染,通常有两种成熟方案。

方案对比

维度 方案一:WebView 桥接 (H5 混合) 方案二:renderjs 视图层穿透
适用场景 复杂的大型 GIS 系统、已有 H5 项目平移 轻量级 GIS、需要原生组件覆盖、对交互流畅度要求极高
开发难度 低(分离开发,通过 URL/PostMessage 交互) 高(需处理逻辑层与 renderjs 层的频繁通信)
调试体验 极佳(可直接在浏览器调试地图逻辑) 一般(依赖真机或内置浏览器模拟)
通信成本 较高(序列化开销大,且单向传输) 较低(支持事件监听,局部更新快)

方案一:WebView 桥接模式(工程解耦版)

核心思想: 将 Cesium 独立为一个 H5 项目发布,App 端通过 <web-view> 组件加载。

1. 逻辑层(App 侧)发送指令

由于 WebView 的 src 变化会引起页面刷新,建议采用 URL 哈希(Hash)或查询参数 传递简单指令。

javascript 复制代码
<web-view 
    ref="webViewRef" 
    :webview-styles="webviewStyles" 
    :style="{ width: '100%'}" 
    :src="src" 
    :cache-mode="'default'"
    @onPostMessage="handleMessage" 
    @message="handleMessage">
</web-view>

1. 浏览器想 App 传值:

javascript 复制代码
uni.postMessage({data:{action:message}});

2. App 接受通信:

javascript 复制代码
handleMessage(e){
    console.log("woshinide1", JSON.stringify(e));
}

3. App 想浏览器传值: 直接把传参写到 src 路径里就行,浏览器解析 URL 就可以获取。

方案 2:通过 RenderJS(重点:恶心玩法来了)

重点来了,最恶心的 RenderJS 玩法来了。

注意: RenderJS 不支持 Vue3 语法糖,还是用 Vue2 玩吧。

1. 静态资源引入

首先在 App 的 static 目录下放入 Cesium 静态文件。

2. 异步加载 Cesium(解决加载阻塞)

然后再 RenderJS 动态引入就行。但由于 Cesium 很大,直接引入会影响生命周期(不是卡死,是它会抢在生命周期引入之前加载)。为此我写了一个异步代码

utils/cesiumLoader.js

javascript 复制代码
/**
 * Cesium 加载器 - 确保全局只有一个加载实例
 */
let loadPromise = null;

export function loadCesium() {
  // 如果已经有加载任务在进行了,直接返回之前的 Promise
  if (loadPromise) return loadPromise;

  loadPromise = new Promise((resolve, reject) => {
    // 1. 如果 window 中已经有了,直接 resolve
    if (typeof window !== 'undefined' && window.Cesium) {
      resolve(window.Cesium);
      return;
    }

    // 2. 动态创建 CSS
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = './static/Cesium/Widgets/widgets.css';
    document.head.appendChild(link);

    // 3. 动态创建 Script
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = './static/Cesium/Cesium.js';
    
    script.onload = () => {
      console.log('Cesium 库加载成功');
      resolve(window.Cesium);
    };

    script.onerror = (err) => {
      loadPromise = null; // 加载失败清除状态,允许重试
      console.error('Cesium 脚本加载失败');
      reject(err);
    };

    document.head.appendChild(script);
  });

  return loadPromise;
}

App.vue 中预加载

javascript 复制代码
import { loadCesium } from '@/utils/cesiumLoader';

export default {
    onLaunch: function() {
        // 预加载一遍
        loadCesium();
    }
};

3. 开始恶心玩法:通信问题

这一步还是最简单玩法。开始恶心玩法通信问题:由于 RenderJS 逻辑层和视图层分离,想在视图层渲染,需要从逻辑层穿透到视图层调用,每个方法相当于对外开发了一个接口,用方法就是调接口,麻烦得很。

我尝试过通过 Bus.js 通信等等,发现都无法将 viewer 抛出。查看官方文档发现一个非常恶心的玩法:

子组件 map.vue 完整代码:
javascript 复制代码
<template>
  <view class="content">
    <view
      id="viewMapContainer"
      :action="action"
      :change:action="cesium.handleAction"
    ></view>
  </view>
</template>

<script>
export default {
  // 逻辑层必须声明 props,模板才能识别
  props: {
    action: {
      type: Object,
      default: () => ({}),
    },
  },
  methods: {
    onMapSuccess(res) {
      if (res.type === 'EXPORT_IMAGE') {
        // 处理回传的图片数据
      }
    }
  },
};
</script>

<script module="cesium" lang="renderjs">
import { loadCesium } from '@/utils/cesiumLoader';
let viewer = null; // 局部变量持久化 viewer

export default {
	props: ['action'],
	async mounted() {
		await loadCesium();
        // 初始化 viewer 的逻辑写在这里...
	},
	methods: {
		handleAction(newValue) {
			if (!newValue || !viewer) return;
            // 通过监听 action 的变化来模拟方法调用
			if (newValue.type === 'export') {
				this.exportImage();
			}
		},
		exportImage() {
			viewer.render();
			const canvas = viewer.scene.canvas;
			const base64Data = canvas.toDataURL('image/png');
			// 将数据传回逻辑层(普通 script)
			this.$ownerInstance.callMethod('onMapSuccess', {
				type: 'EXPORT_IMAGE',
				data: base64Data
			});
		}
	}
};
</script>

<style>
.content {
  position: relative;
  width: 100vw;
  height: 100vh;
}
#viewMapContainer {
  position: absolute;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  z-index: 1;
}
</style>

父组件 index.vue 完整代码:

javascript 复制代码
<template>
	<view class="content">
		<mapVue ref="mapRef" :action="currentAction" />
        <view @click="exportImage">
		
	</view>
</template>

<script module="index">
	export default {
		data() {
			return {
				currentAction: {},

			};
		},
		components: {
			mapVue,
		},
		async mounted() {

		},
		methods: {
			//导出图
			async exportImage() {
				this.currentAction = {
					type: 'export',
					data: {},
					_t: Date.now() 
				};
			},
		
		},
	};
</script>
相关推荐
jingling5552 小时前
uni-app 安卓端完美接入卫星地图:解决图层缺失与层级过高难题
android·前端·javascript·uni-app
2501_9159184119 小时前
iOS 开发中证书创建与管理中的常见问题
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张20 小时前
IOScer 开发环境证书包括哪些,证书、描述文件与 App ID 的协同管理实践
android·ios·小程序·https·uni-app·iphone·webview
天府之绝20 小时前
Uniapp App(Android)端 非媒体文件的选择、上传、下载、查看功能
uni-app
ZEGO即构开发者1 天前
uni-app 集成音视频 SDK 全攻略:30 分钟搭建跨端视频通话功能
uni-app·音视频·视频通话功能
Hzsilvana1 天前
在 uni-app 中检测 APP 端是否有通知权限
uni-app
咸虾米_1 天前
uniapp使用history路由模式打包上线到前端网页托管的注意事项
前端·uni-app·vue3·unicloud·前端网页托管
2501_915921431 天前
iPhone HTTPS 抓包在真机环境下面临的常见问题
android·ios·小程序·https·uni-app·iphone·webview
未寒1 天前
关于uni app vue2 和vue3 的区别
前端·javascript·vue.js·uni-app