概述
将Three.js应用打包为桌面端或移动端应用可以显著扩展用户群体和使用场景。本节将深入讲解如何使用Electron和Cordova将Three.js Web应用打包为跨平台的桌面和移动应用。

跨平台打包架构:
Electron桌面应用打包
Electron基础架构
| 组件 | 职责 | 技术实现 |
|---|---|---|
| 主进程 | 应用生命周期、窗口管理 | Node.js环境 |
| 渲染进程 | UI渲染、Three.js运行 | Chromium浏览器 |
| 预加载脚本 | 安全通信桥梁 | 受限Node.js API |
| IPC通信 | 进程间数据交换 | 事件驱动模式 |
完整Electron项目配置
1. 项目结构设计
threejs-electron-app/
├── package.json
├── electron/
│ ├── main.js # 主进程
│ ├── preload.js # 预加载脚本
│ └── helpers.js # 工具函数
├── src/
│ ├── index.html # 主页面
│ ├── css/
│ ├── js/
│ │ ├── app.js # Three.js应用
│ │ └── utils.js
│ └── assets/ # 静态资源
├── build/ # 构建输出
└── dist/ # 分发包
2. 主进程配置 (electron/main.js)
const { app, BrowserWindow, Menu, ipcMain, shell, dialog } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
class ThreeJSApp {
constructor() {
this.mainWindow = null;
this.init();
}
init() {
// 应用准备就绪
app.whenReady().then(() => {
this.createWindow();
this.setMenu();
this.setIPC();
});
// 所有窗口关闭
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
this.createWindow();
}
});
}
createWindow() {
this.mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
webSecurity: !isDev,
allowRunningInsecureContent: isDev
},
icon: path.join(__dirname, '../assets/icons/icon.png'),
show: false,
titleBarStyle: 'default'
});
// 加载应用
const startUrl = isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../build/index.html')}`;
this.mainWindow.loadURL(startUrl);
// 窗口准备好后显示
this.mainWindow.once('ready-to-show', () => {
this.mainWindow.show();
if (isDev) {
this.mainWindow.webContents.openDevTools();
}
});
// 处理外部链接
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
// 窗口关闭事件
this.mainWindow.on('closed', () => {
this.mainWindow = null;
});
}
setMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '新建场景',
accelerator: 'CmdOrCtrl+N',
click: () => {
this.mainWindow.webContents.send('menu-new-scene');
}
},
{
label: '导出场景',
accelerator: 'CmdOrCtrl+E',
click: () => {
this.mainWindow.webContents.send('menu-export-scene');
}
},
{ type: 'separator' },
{
label: '退出',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
}
]
},
{
label: '视图',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
{
label: '帮助',
submenu: [
{
label: '关于',
click: () => {
this.showAboutDialog();
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
setIPC() {
// 处理文件保存
ipcMain.handle('save-file', async (event, data) => {
const result = await dialog.showSaveDialog(this.mainWindow, {
defaultPath: 'scene.json',
filters: [
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled) {
const fs = require('fs').promises;
try {
await fs.writeFile(result.filePath, JSON.stringify(data, null, 2));
return { success: true, path: result.filePath };
} catch (error) {
return { success: false, error: error.message };
}
}
return { success: false, error: '用户取消' };
});
// 处理文件加载
ipcMain.handle('load-file', async (event) => {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled) {
const fs = require('fs').promises;
try {
const data = await fs.readFile(result.filePaths[0], 'utf8');
return { success: true, data: JSON.parse(data) };
} catch (error) {
return { success: false, error: error.message };
}
}
return { success: false, error: '用户取消' };
});
// 获取应用信息
ipcMain.handle('get-app-info', () => {
return {
version: app.getVersion(),
platform: process.platform,
isDev: isDev
};
});
}
showAboutDialog() {
dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: '关于',
message: 'Three.js 3D应用',
detail: `版本: ${app.getVersion()}\nElectron: ${process.versions.electron}\nChromium: ${process.versions.chrome}\nNode.js: ${process.versions.node}`
});
}
}
// 启动应用
new ThreeJSApp();
3. 预加载脚本 (electron/preload.js)
const { contextBridge, ipcRenderer } = require('electron');
// 暴露安全的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 文件操作
saveFile: (data) => ipcRenderer.invoke('save-file', data),
loadFile: () => ipcRenderer.invoke('load-file'),
// 应用信息
getAppInfo: () => ipcRenderer.invoke('get-app-info'),
// 菜单事件
onMenuNewScene: (callback) => ipcRenderer.on('menu-new-scene', callback),
onMenuExportScene: (callback) => ipcRenderer.on('menu-export-scene', callback),
// 移除监听器
removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel)
});
// 在加载时初始化
window.addEventListener('DOMContentLoaded', () => {
console.log('预加载脚本执行完成');
});
4. Three.js应用适配 (src/js/app.js)
class ThreeJSDesktopApp {
constructor() {
this.scene = null;
this.camera = null;
this.renderer = null;
this.isElectron = false;
this.init();
}
async init() {
// 检查运行环境
this.isElectron = !!(window.electronAPI);
// 初始化Three.js
await this.initThreeJS();
// 设置Electron特定功能
if (this.isElectron) {
await this.setupElectronFeatures();
}
// 启动渲染循环
this.animate();
}
async initThreeJS() {
// 创建场景
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x1a1a2e);
// 创建相机
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(5, 5, 5);
this.camera.lookAt(0, 0, 0);
// 创建渲染器
this.renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance"
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('app').appendChild(this.renderer.domElement);
// 添加光源
this.setupLighting();
// 添加示例几何体
this.createDemoScene();
// 窗口大小调整
window.addEventListener('resize', () => this.onWindowResize());
}
setupLighting() {
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = true;
this.scene.add(directionalLight);
}
createDemoScene() {
// 创建地面
const groundGeometry = new THREE.PlaneGeometry(10, 10);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a3a4a,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
// 创建立方体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({
color: 0xff6b6b,
roughness: 0.7,
metalness: 0.3
});
this.cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
this.cube.position.y = 0.5;
this.cube.castShadow = true;
this.scene.add(this.cube);
// 创建球体
const sphereGeometry = new THREE.SphereGeometry(0.5, 16, 16);
const sphereMaterial = new THREE.MeshStandardMaterial({
color: 0x4ecdc4,
roughness: 0.6,
metalness: 0.4
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(2, 0.5, 0);
sphere.castShadow = true;
this.scene.add(sphere);
}
async setupElectronFeatures() {
try {
// 获取应用信息
const appInfo = await window.electronAPI.getAppInfo();
this.updateAppTitle(appInfo);
// 监听菜单事件
window.electronAPI.onMenuNewScene(() => {
this.resetScene();
});
window.electronAPI.onMenuExportScene(() => {
this.exportScene();
});
// 添加快捷键支持
this.setupKeyboardShortcuts();
} catch (error) {
console.error('Electron功能设置失败:', error);
}
}
updateAppTitle(appInfo) {
const title = `Three.js桌面应用 - v${appInfo.version} (${appInfo.platform})`;
document.title = title;
// 更新UI显示
const platformInfo = document.getElementById('platform-info');
if (platformInfo) {
platformInfo.textContent = `运行在 ${appInfo.platform} ${appInfo.isDev ? '开发模式' : '生产模式'}`;
}
}
setupKeyboardShortcuts() {
document.addEventListener('keydown', (event) => {
// Ctrl+S / Cmd+S 保存场景
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault();
this.exportScene();
}
// Ctrl+O / Cmd+O 加载场景
if ((event.ctrlKey || event.metaKey) && event.key === 'o') {
event.preventDefault();
this.loadScene();
}
// Ctrl+N / Cmd+N 新建场景
if ((event.ctrlKey || event.metaKey) && event.key === 'n') {
event.preventDefault();
this.resetScene();
}
});
}
async exportScene() {
if (!this.isElectron) return;
const sceneData = {
metadata: {
version: '1.0',
generator: 'Three.js Desktop App',
exportedAt: new Date().toISOString()
},
objects: this.collectSceneObjects()
};
try {
const result = await window.electronAPI.saveFile(sceneData);
if (result.success) {
this.showNotification(`场景已保存到: ${result.path}`);
} else {
this.showNotification(`保存失败: ${result.error}`, 'error');
}
} catch (error) {
this.showNotification(`保存错误: ${error.message}`, 'error');
}
}
async loadScene() {
if (!this.isElectron) return;
try {
const result = await window.electronAPI.loadFile();
if (result.success) {
this.loadSceneData(result.data);
this.showNotification('场景加载成功');
} else {
this.showNotification(`加载失败: ${result.error}`, 'error');
}
} catch (error) {
this.showNotification(`加载错误: ${error.message}`, 'error');
}
}
collectSceneObjects() {
const objects = [];
this.scene.traverse((object) => {
if (object.isMesh) {
objects.push({
type: object.geometry.type,
position: object.position.toArray(),
rotation: object.rotation.toArray(),
scale: object.scale.toArray(),
material: {
color: object.material.color.getHex(),
roughness: object.material.roughness,
metalness: object.material.metalness
}
});
}
});
return objects;
}
loadSceneData(data) {
// 清空当前场景
while(this.scene.children.length > 0) {
this.scene.remove(this.scene.children[0]);
}
// 重新创建场景
this.setupLighting();
// 加载保存的对象
if (data.objects) {
data.objects.forEach(objData => {
this.createObjectFromData(objData);
});
}
}
createObjectFromData(objData) {
let geometry;
switch (objData.type) {
case 'BoxGeometry':
geometry = new THREE.BoxGeometry(1, 1, 1);
break;
case 'SphereGeometry':
geometry = new THREE.SphereGeometry(0.5, 16, 16);
break;
default:
geometry = new THREE.BoxGeometry(1, 1, 1);
}
const material = new THREE.MeshStandardMaterial({
color: objData.material.color,
roughness: objData.material.roughness,
metalness: objData.material.metalness
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.fromArray(objData.position);
mesh.rotation.fromArray(objData.rotation);
mesh.scale.fromArray(objData.scale);
mesh.castShadow = true;
this.scene.add(mesh);
}
resetScene() {
// 清空场景
while(this.scene.children.length > 0) {
this.scene.remove(this.scene.children[0]);
}
// 重新初始化
this.setupLighting();
this.createDemoScene();
this.showNotification('场景已重置');
}
showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
// 添加到页面
document.body.appendChild(notification);
// 自动移除
setTimeout(() => {
notification.remove();
}, 3000);
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
animate() {
requestAnimationFrame(() => this.animate());
// 旋转立方体
if (this.cube) {
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
}
this.renderer.render(this.scene, this.camera);
}
}
// 启动应用
new ThreeJSDesktopApp();
5. 构建配置 (package.json)
{
"name": "threejs-desktop-app",
"version": "1.0.0",
"description": "Three.js桌面3D应用",
"main": "electron/main.js",
"scripts": {
"electron": "electron .",
"electron-dev": "ELECTRON_IS_DEV=true electron .",
"build-web": "webpack --mode=production",
"build-electron": "electron-builder",
"pack": "electron-builder --dir",
"dist": "npm run build-web && electron-builder",
"postinstall": "electron-builder install-app-deps"
},
"build": {
"appId": "com.yourcompany.threejs-app",
"productName": "Three.js 3D应用",
"directories": {
"output": "dist-electron"
},
"files": [
"build/**/*",
"electron/**/*",
"node_modules/**/*",
"package.json"
],
"mac": {
"category": "public.app-category.graphics-design",
"icon": "assets/icons/icon.icns",
"target": "dmg"
},
"win": {
"icon": "assets/icons/icon.ico",
"target": "nsis"
},
"linux": {
"icon": "assets/icons/icon.png",
"target": "AppImage"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"devDependencies": {
"electron": "^22.0.0",
"electron-builder": "^24.0.0",
"electron-is-dev": "^2.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0"
},
"dependencies": {
"three": "^0.150.0"
}
}
Cordova移动应用打包
Cordova项目配置
1. 创建Cordova项目
# 安装Cordova CLI
npm install -g cordova
# 创建项目
cordova create ThreeJSMobileApp com.yourcompany.threejsapp ThreeJSMobileApp
cd ThreeJSMobileApp
# 添加平台
cordova platform add android
cordova platform add ios
# 添加插件
cordova plugin add cordova-plugin-device
cordova plugin add cordova-plugin-screen-orientation
cordova plugin add cordova-plugin-statusbar
cordova plugin add cordova-plugin-splashscreen
2. Three.js移动端适配 (www/js/app.js)
class ThreeJSMobileApp {
constructor() {
this.scene = null;
this.camera = null;
this.renderer = null;
this.controls = null;
this.touchStart = { x: 0, y: 0 };
this.rotation = { x: 0, y: 0 };
this.init();
}
async init() {
// 等待设备就绪
if (window.cordova) {
document.addEventListener('deviceready', () => this.onDeviceReady(), false);
} else {
this.onDeviceReady();
}
}
onDeviceReady() {
// 设置屏幕方向
screen.orientation.lock('landscape');
// 初始化Three.js
this.initThreeJS();
// 设置触摸控制
this.setupTouchControls();
// 设置性能监控
this.setupPerformanceMonitor();
// 启动渲染循环
this.animate();
}
initThreeJS() {
// 获取视口尺寸
const width = window.innerWidth;
const height = window.innerHeight;
// 创建场景
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x1a1a2e);
this.scene.fog = new THREE.Fog(0x1a1a2e, 20, 100);
// 创建相机 - 移动端使用更宽的视野
this.camera = new THREE.PerspectiveCamera(70, width / height, 0.1, 1000);
this.camera.position.set(0, 5, 10);
// 创建渲染器 - 移动端优化
this.renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance",
alpha: false
});
this.renderer.setSize(width, height);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制像素比
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 添加到DOM
document.getElementById('app').appendChild(this.renderer.domElement);
// 设置光源
this.setupLighting();
// 创建场景内容
this.createScene();
// 窗口大小调整
window.addEventListener('resize', () => this.onWindowResize(), false);
// 处理返回按钮
document.addEventListener('backbutton', () => this.onBackButton(), false);
}
setupLighting() {
// 环境光
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
this.scene.add(ambientLight);
// 方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
this.scene.add(directionalLight);
}
createScene() {
// 创建地面
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x3a3a4a,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
// 创建多个物体
this.createObjects();
}
createObjects() {
const geometries = [
new THREE.BoxGeometry(1, 1, 1),
new THREE.SphereGeometry(0.5, 16, 16),
new THREE.ConeGeometry(0.5, 1, 16),
new THREE.CylinderGeometry(0.5, 0.5, 1, 16),
new THREE.TorusGeometry(0.5, 0.2, 16, 32)
];
const colors = [0xff6b6b, 0x4ecdc4, 0x45b7d1, 0x96ceb4, 0xffe66d];
geometries.forEach((geometry, index) => {
const material = new THREE.MeshStandardMaterial({
color: colors[index],
roughness: 0.7,
metalness: 0.3
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(
(index - 2) * 2.5,
1,
0
);
mesh.castShadow = true;
mesh.userData = { originalY: 1, speed: 0.5 + Math.random() * 1.0 };
this.scene.add(mesh);
});
}
setupTouchControls() {
const canvas = this.renderer.domElement;
// 触摸开始
canvas.addEventListener('touchstart', (event) => {
event.preventDefault();
const touch = event.touches[0];
this.touchStart.x = touch.clientX;
this.touchStart.y = touch.clientY;
}, { passive: false });
// 触摸移动 - 旋转控制
canvas.addEventListener('touchmove', (event) => {
event.preventDefault();
if (event.touches.length === 1) {
const touch = event.touches[0];
const deltaX = touch.clientX - this.touchStart.x;
const deltaY = touch.clientY - this.touchStart.y;
this.rotation.y += deltaX * 0.01;
this.rotation.x += deltaY * 0.01;
this.touchStart.x = touch.clientX;
this.touchStart.y = touch.clientY;
}
}, { passive: false });
// 双指缩放
let initialDistance = 0;
canvas.addEventListener('touchstart', (event) => {
if (event.touches.length === 2) {
initialDistance = this.getTouchDistance(event.touches[0], event.touches[1]);
}
}, { passive: false });
canvas.addEventListener('touchmove', (event) => {
if (event.touches.length === 2) {
event.preventDefault();
const currentDistance = this.getTouchDistance(event.touches[0], event.touches[1]);
const zoomDelta = (currentDistance - initialDistance) * 0.01;
this.camera.position.z = THREE.MathUtils.clamp(
this.camera.position.z - zoomDelta,
5,
50
);
initialDistance = currentDistance;
}
}, { passive: false });
}
getTouchDistance(touch1, touch2) {
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
setupPerformanceMonitor() {
// 创建FPS显示
this.stats = {
fps: 0,
frameCount: 0,
lastTime: performance.now()
};
const statsElement = document.createElement('div');
statsElement.style.cssText = `
position: fixed;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
padding: 5px 10px;
border-radius: 5px;
font-family: Arial, sans-serif;
font-size: 12px;
z-index: 1000;
`;
document.body.appendChild(statsElement);
this.statsElement = statsElement;
}
updatePerformanceStats() {
this.stats.frameCount++;
const currentTime = performance.now();
if (currentTime >= this.stats.lastTime + 1000) {
this.stats.fps = Math.round((this.stats.frameCount * 1000) / (currentTime - this.stats.lastTime));
this.stats.frameCount = 0;
this.stats.lastTime = currentTime;
if (this.statsElement) {
this.statsElement.textContent = `FPS: ${this.stats.fps}`;
}
}
}
onWindowResize() {
const width = window.innerWidth;
const height = window.innerHeight;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
}
onBackButton() {
// 处理Android返回按钮
if (confirm('确定要退出应用吗?')) {
navigator.app.exitApp();
}
}
animate() {
requestAnimationFrame(() => this.animate());
// 更新相机旋转
this.camera.position.x = 10 * Math.sin(this.rotation.y);
this.camera.position.z = 10 * Math.cos(this.rotation.y);
this.camera.lookAt(0, 0, 0);
// 物体动画
this.scene.traverse((object) => {
if (object.isMesh && object.userData.originalY !== undefined) {
object.position.y = object.userData.originalY + Math.sin(Date.now() * 0.001 * object.userData.speed) * 0.5;
object.rotation.y += 0.01 * object.userData.speed;
}
});
// 更新性能统计
this.updatePerformanceStats();
// 渲染场景
this.renderer.render(this.scene, this.camera);
}
}
// 启动应用
new ThreeJSMobileApp();
3. 移动端配置 (config.xml)
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.yourcompany.threejsapp" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Three.js 3D应用</name>
<description>基于Three.js的移动端3D应用</description>
<author email="your@email.com" href="http://yourwebsite.com">Your Name</author>
<!-- 支持的方向 -->
<preference name="Orientation" value="landscape" />
<!-- 全屏显示 -->
<preference name="Fullscreen" value="true" />
<!-- 状态栏设置 -->
<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="StatusBarBackgroundColor" value="#000000" />
<preference name="StatusBarStyle" value="lightcontent" />
<!-- 启动画面 -->
<preference name="AutoHideSplashScreen" value="true" />
<preference name="SplashScreenDelay" value="3000" />
<preference name="FadeSplashScreen" value="true" />
<preference name="FadeSplashScreenDuration" value="500" />
<!-- Android配置 -->
<platform name="android">
<allow-intent href="market:*" />
<preference name="AndroidInsecureFileModeEnabled" value="true" />
<!-- 图标 -->
<icon src="res/icon/android/icon-36-ldpi.png" density="ldpi" />
<icon src="res/icon/android/icon-48-mdpi.png" density="mdpi" />
<icon src="res/icon/android/icon-72-hdpi.png" density="hdpi" />
<icon src="res/icon/android/icon-96-xhdpi.png" density="xhdpi" />
<!-- 启动画面 -->
<splash src="res/screen/android/splash-land-hdpi.png" density="land-hdpi"/>
<splash src="res/screen/android/splash-land-ldpi.png" density="land-ldpi"/>
<splash src="res/screen/android/splash-land-mdpi.png" density="land-mdpi"/>
<splash src="res/screen/android/splash-land-xhdpi.png" density="land-xhdpi"/>
</platform>
<!-- iOS配置 -->
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<!-- 图标 -->
<icon src="res/icon/ios/icon.png" width="57" height="57" />
<icon src="res/icon/ios/icon@2x.png" width="114" height="114" />
<icon src="res/icon/ios/icon-40.png" width="40" height="40" />
<icon src="res/icon/ios/icon-40@2x.png" width="80" height="80" />
<!-- 启动画面 -->
<splash src="res/screen/ios/Default-Landscape~iphone.png" width="480" height="320"/>
<splash src="res/screen/ios/Default-Landscape@2x~iphone.png" width="960" height="640"/>
</platform>
<!-- 插件 -->
<plugin name="cordova-plugin-whitelist" spec="1" />
<plugin name="cordova-plugin-device" spec="2" />
<plugin name="cordova-plugin-screen-orientation" spec="3" />
<plugin name="cordova-plugin-statusbar" spec="2" />
<plugin name="cordova-plugin-splashscreen" spec="6" />
<!-- 访问权限 -->
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
</widget>
性能优化与调试
跨平台性能优化策略
| 平台 | 性能挑战 | 优化方案 |
|---|---|---|
| Electron | 内存占用高 | 代码分割、懒加载 |
| Cordova Android | GPU性能差异 | 简化着色器、LOD |
| Cordova iOS | 内存限制严格 | 纹理压缩、对象池 |
调试技巧
// 跨平台调试工具
class CrossPlatformDebugger {
static log(message, data = null) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}`;
// 控制台输出
console.log(logMessage, data || '');
// Electron主进程日志
if (window.electronAPI) {
window.electronAPI.sendLog(logMessage, data);
}
// Cordova设备日志
if (window.cordova && console.log) {
console.log(logMessage);
}
}
static performanceMark(name) {
if (performance.mark) {
performance.mark(`start-${name}`);
}
}
static performanceMeasure(name) {
if (performance.measure) {
performance.measure(name, `start-${name}`);
const measures = performance.getEntriesByName(name);
const duration = measures[measures.length - 1]?.duration;
this.log(`Performance ${name}: ${duration?.toFixed(2)}ms`);
}
}
static memoryUsage() {
if (window.performance && performance.memory) {
const memory = performance.memory;
this.log('Memory Usage', {
used: Math.round(memory.usedJSHeapSize / 1048576) + 'MB',
total: Math.round(memory.totalJSHeapSize / 1048576) + 'MB',
limit: Math.round(memory.jsHeapSizeLimit / 1048576) + 'MB'
});
}
}
}
通过Electron和Cordova,我们可以将Three.js Web应用打包为功能完整的桌面和移动应用,充分利用各平台的特性和能力,为用户提供原生应用般的体验。

1919

被折叠的 条评论
为什么被折叠?



