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>
相关推荐
游戏开发爱好者87 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
2501_915106329 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
2501_915106329 小时前
使用 Sniffmaster TCP 抓包和 Wireshark 网络分析
网络协议·tcp/ip·ios·小程序·uni-app·wireshark·iphone
宠友信息10 小时前
2025社交+IM及时通讯社区APP仿小红书小程序
java·spring boot·小程序·uni-app·web app
“负拾捌”11 小时前
python + uniapp 结合腾讯云实现实时语音识别功能(WebSocket)
python·websocket·微信小程序·uni-app·大模型·腾讯云·语音识别
局外人LZ1 天前
Uniapp脚手架项目搭建,uniapp+vue3+uView pro+vite+pinia+sass
前端·uni-app·sass
2501_915918411 天前
在 iOS 环境下查看 App 详细信息与文件目录
android·ios·小程序·https·uni-app·iphone·webview
前端呆头鹅1 天前
Websocket使用方案详解(uniapp版)
websocket·网络协议·uni-app
浮桥1 天前
uniapp+h5 公众号实现分享海报绘制
uni-app·notepad++
2501_916007471 天前
没有 Mac 用户如何上架 App Store,IPA生成、证书与描述文件管理、跨平台上传
android·macos·ios·小程序·uni-app·iphone·webview