threejs + vite + vue3 数字孪生简单案例
一、安装必要的软件
3D项目创建: npm create vite@latest task-hub -- --template vue npm create vite@latest:拉取最新版 Vite 脚手架创建项目 task-hub:项目文件夹名称(生成在当前目录) --:分隔 npm 参数与 Vite 专属参数,不能省略 --template vue:指定模板为纯 Vue3(不含 TS、JSX 等) npm install vue-router 安装路由 npm install three --save # three 生产依赖 npm install @types/three -D # 类型声明 开发依赖 npm install ant-design-vue 业务 UI 组件库 npm install typescript --save-dev 等价 npm install typescript -D npm install stats.js //帧率监控,性能监控 npm install @types/stats.js -D //开发依赖 // 运行依赖,页面要实际使用控制面板 Three.js/ WebGL 开发专用可视化调试控制面板,快速拖拽修改参数,不用反复改代码刷新页面。 专门调 Three.js、3D 场景、动画、光照、模型参数,普通用户不会看到这个面板。 npm install dat.gui npm install @types/dat.gui -D npm install gsap 补间动画二、文件内容
文件结构如下:
tian@hang:~/ThreejsEngineer$ tree -L 3 . ├── index.html ....... ├── package.json ├── package-lock.json ├── public │ ├── favicon.svg │ └── icons.svg ├── README.md ├── src │ ├── App.vue │ ├── assets │ │ ├── css │ │ └── img │ ├── components │ │ └── ThreeView.vue │ ├── lesson │ │ └── Home.vue │ ├── main │ │ └── main.js │ └── router │ └── index.ts └── vite.config.js tian@hang:~/ThreejsEngineer$ ls index.html node_modules package.json package-lock.json public README.md src vite.config.js内容如下:
package.json内容 { "name": "task-hub", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview --host" }, "dependencies": { "ant-design-vue": "^4.2.6", "dat.gui": "^0.7.9", "gsap": "^3.15.0", "stats.js": "^0.17.0", "three": "^0.185.0", "vue": "^3.5.39", "vue-router": "^5.1.0" }, "devDependencies": { "@types/stats.js": "^0.17.4", "@types/three": "^0.185.0", "@vitejs/plugin-vue": "^6.0.7", "@vue/tsconfig": "^0.9.1", "typescript": "^6.0.3", "vite": "^8.1.1", "vue-tsc": "^3.3.6" } } index.html内容: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="./src/assets/img/TSK.png" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>工业系统</title> </head> <body> <div id="app"></div> <script type="module" src="./src/main/main.js"></script> </body> </html> main.js 内容 import { createApp } from 'vue' import '../assets/css/style.css' import App from '../App.vue' createApp(App).mount('#app') Home.vue 内容 <template> <div> <h2>Three.js 演示</h2> <ThreeView /> </div> </template> <script setup lang="ts"> import ThreeView from '../components/ThreeView.vue' </script> App.vue内容 <script setup> import Home from './lesson/Home.vue' </script> <template> <Home /> </template> ThreeView.vue内容 <template> <!-- 专属容器,不再直接挂body --> <div ref="canvasContainer" style="width:100%;height:600px;border:1px solid #eee;"></div> </template> <script setup lang="ts"> import { ref, onMounted, onUnmounted } from 'vue' import * as THREE from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import { gsap } from 'gsap' // DOM容器ref const canvasContainer = ref<HTMLDivElement | null>(null) // 全局场景变量,统一管理,卸载时销毁 let scene: THREE.Scene let camera: THREE.PerspectiveCamera let renderer: THREE.WebGLRenderer let controls: OrbitControls let cube: THREE.Mesh let animateId: number let gsapTween: gsap.core.Tween // 窗口resize处理函数 const handleResize = () => { if (!canvasContainer.value) return const width = canvasContainer.value.clientWidth const height = canvasContainer.value.clientHeight camera.aspect = width / height camera.updateProjectionMatrix() renderer.setSize(width, height) renderer.setPixelRatio(window.devicePixelRatio) } // 双击全屏 const handleDblClick = () => { if (!document.fullscreenElement) { renderer.domElement.requestFullscreen() } else { document.exitFullscreen() } } // 渲染循环 const renderLoop = () => { animateId = requestAnimationFrame(renderLoop) controls.update() renderer.render(scene, camera) } // 挂载时初始化3D场景 onMounted(() => { if (!canvasContainer.value) return // 1. 场景 scene = new THREE.Scene() // 2. 相机 camera = new THREE.PerspectiveCamera( 75, canvasContainer.value.clientWidth / canvasContainer.value.clientHeight, 0.1, 1000 ) camera.position.set(0, 0, 10) scene.add(camera) // 3. 立方体 const boxGeo = new THREE.BoxGeometry(1, 1, 1) const mat = new THREE.MeshBasicMaterial({ color: 0xffff00 }) cube = new THREE.Mesh(boxGeo, mat) scene.add(cube) // 4. 渲染器,挂载到vue容器,不是body renderer = new THREE.WebGLRenderer() renderer.setSize(canvasContainer.value.clientWidth, canvasContainer.value.clientHeight) canvasContainer.value.appendChild(renderer.domElement) // 5. 轨道控制器 controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true // 6. gsap动画 gsapTween = gsap.to(cube.position, { x: 5, duration: 5, repeat: -1, yoyo: true, ease: "bounce.out", onComplete: () => console.log("动画完成"), onStart: () => console.log("动画开始") }) // 7. 坐标轴辅助 const axesHelper = new THREE.AxesHelper(8) scene.add(axesHelper) // 8. 监听事件 window.addEventListener('resize', handleResize) renderer.domElement.addEventListener('dblclick', handleDblClick) // 启动渲染 renderLoop() }) // 组件销毁,释放资源(关键!防止内存泄漏) onUnmounted(() => { // 停止渲染循环 cancelAnimationFrame(animateId) // 停止gsap动画 gsapTween.kill() // 移除监听 window.removeEventListener('resize', handleResize) renderer.domElement.removeEventListener('dblclick', handleDblClick) // 销毁渲染器、几何体、材质释放显存 renderer.dispose() cube.geometry.dispose() if (Array.isArray(cube.material)) { cube.material.forEach(m => m.dispose()) } else { cube.material.dispose() } }) </script>三、服务器部署
(1)文件打包:npm run build
tian@hang:~/ThreejsEngineer$ npm run build > task-hub@0.0.0 build > vite build vite v8.1.2 building client environment for production... ✓ 21 modules transformed. computing gzip size... dist/index.html 0.48 kB │ gzip: 0.34 kB dist/assets/TSK-BnnPvGX4.png 10.62 kB dist/assets/index-Bzyp_i7E.css 0.04 kB │ gzip: 0.06 kB dist/assets/index-UVdWu6yE.js 671.57 kB │ gzip: 185.80 kB [plugin builtin:vite-reporter] (!) Some chunks are larger than 500 kB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. ✓ built in 672ms(2)文件传输
/home/tian/ThreejsEngineer/dist/ :文件在主机的位置
kickpi@192.168.31.224:/home/kickpi/myvue3 :服务器用户名;ip地址、文件放置服务器的位置
scp -r /home/tian/ThreejsEngineer/dist/ kickpi@192.168.31.224:/home/kickpi/myvue3(3)权限配置
服务器第一次没起起来,检查时权限问题;做了如下操作
sudo mkdir -p /www/myvue3 sudo cp -r /home/kickpi/myvue3/dist/* /www/myvue3/ sudo chown -R www-data:www-data /www/myvue3 sudo chmod -R 755 /www/myvue3(4)安装服务器
用到的命令如下:
sudo apt update sudo apt install nginx -y //安装服务器 nginx -v //查看版本 sudo systemctl start nginx # 启动服务 sudo systemctl enable nginx # 设置开机自启 sudo ufw allow 80/tcp # 放行80端口 **kickpi不用** sudo systemctl status nginx # 查看运行状态 sudo systemctl reload nginx.service #重新加载 ;更改文件后需要 sudo nginx -t # 校验配置语法(5)文件增加
sudo vim /etc/nginx/conf.d/taskhub.conf
sudo vim /etc/nginx/conf.d/taskhub.conf 文件内容: server { listen 5210; # 填你的域名或服务器公网IP server_name myvue3.com 192.168.31.224; # 上传dist的目录 root /www/myvue3; index index.html; # 关键:Vue Router history模式刷新404解决方案 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存,提升加载速度 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 7d; add_header Cache-Control "public"; } }