【Threejs进阶教程-优化篇】4.Vue/React与threejs如何解决冲突和卡顿(续)

Vue/React与threejs如何解决冲突和卡顿-续

  • 使用说明
  • 核心思路
  • 环境搭建(vue+three)
  • vue运行机制分析
  • 业务分离
    • 使用threejs做背景
  • 3D模块封装
    • 使用ES6的Class来让逻辑性更强
    • Threejs尽量按需引入
    • 创建一个类
    • 扩展写法
    • 本次代码执行顺序
  • 扩展内容
    • 添加orbitControls和辅助线
    • 解决事件覆盖
  • 与Vue交互
    • 添加操作代码
    • 绑定ThreeCore到vue原型或window上
    • 在vue中调用函数
  • 协同开发
  • 全框架可行性说明
  • 源码文件已上传,还有有不懂的问题可以在下方留言

上一篇地址
不少人反映,上一篇中讲解的不够详细,或者难以看懂,这一篇我们从0开始来搭建一个Vue+Three的项目

使用说明

  1. 本方案适用于任何条件下任何框架,不受vue,react,angular等版本限制
  2. 本文着重讲解思路,部分代码可能不适用于TS
  3. 本篇教程中使用到了ES6的Class知识,对es6不熟的请优先补一下ES6的相关知识
  4. 本人并不熟悉vue3,所以部分写法比较倾向于vue2,这部分代码请各位自行调整

核心思路

  1. 彻底跳出vue的视界,在js层面来解决冲突
  2. vue只用来处理dom,threejs只用来处理画布内容
  3. 单例化并接口化three部分,参考前后端分离的 dom-canvas分离方案

环境搭建(vue+three)

这一部分基本上都是前端基本功,再不行百度一下也行

  1. 安装vue,这个过程本人就不解释了,本篇教程使用vue create vue-three来创建项目,本篇并不是在讲vue,所以vue的细节部分就不多说了,按照自己喜欢的配置即可
  2. 安装任意版本的Threejs, npm i three,本人使用当前最新版166
  3. node版本: 20.15.0
  4. npm版本: 10.8.1
  5. yarn版本: 1.22.22,本人主要使用yarn来安装依赖
  6. vue/cli版本 5.0.8
  7. 其他库版本

下面是本人的初始文件结构和package.json
在这里插入图片描述

vue运行机制分析

在这里插入图片描述
首先,我们现在关注这两个文件,一个是index.html,一个是main.js

任何的js程序,都应该有一个入口程序,而在vue中,入口程序不是 app.vue,而是main.js,我们来分析main.js的代码

createApp() 从字面意思上是创建vueApplication
然后参数中的mount() 用于获取页面中指定id的dom,也就是index.html中的 < div id=‘app’> 的这个div

也就是说,在mainjs中,vue的脚手架只干了一件事,从页面中读取到id为app的div,然后将vue文件编译后,生成dom,并填充这个div

在这里插入图片描述
我们把项目跑起来,用dom检查来检查这个div,内容基本上是由helloworld.vue文件提供

也就是说,vue其实并没有脱离html + css + js这个系统,而是用自己的系统单独在处理一个div

业务分离

既然是这样,那我们就可以完全视为原生的方式来开发,避免vue系统与threejs出现冲突

在这里插入图片描述
我们在main.js中,加入threejs的Helloworld的代码

//main.js
import { createApp } from 'vue'
import App from './App.vue'
import * as THREE from "three";createApp(App).mount('#app')let scene = new THREE.Scene()let camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);
camera.position.z += 5;
let renderer = new THREE.WebGLRenderer({alpha:true
});
renderer.setSize(window.innerWidth,window.innerHeight);
document.body.appendChild(renderer.domElement);let geometry = new THREE.BoxGeometry(1,1,1);
let material = new THREE.MeshBasicMaterial({color:0xff0000
});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);render();
function render() {renderer.render(scene,camera);requestAnimationFrame(render);mesh.rotation.x += 0.01;mesh.rotation.y += 0.01;
}

此时,我们发现,页面上出现了画布,也出现了跳动的方块,但是,位置不对
在这里插入图片描述

使用threejs做背景

这个问题非常简单,让app和threejs的内容均设定为absolute定位,然后threejs的canvas层级比id为app的div低即可
在这里插入图片描述
在app.vue中添加

#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;position: absolute;/*修改app的position*/z-index: 10;/*建议大于10*/
}

在main.js中添加,追加位置如图所示

//追加代码
renderer.domElement.style.position = "absolute";
renderer.domElement.style.top = "0";
renderer.domElement.style.left = "0";//追加自适应代码
//自适应代码
window.addEventListener('resize',()=>{renderer.setSize(window.innerWidth,window.innerHeight);camera.aspect = window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();
});

在这里插入图片描述
这时候再来看效果,基本上就已经做到了基本的融合了

3D模块封装

我们总不可能把所有的代码都挤到main.js中,一般来讲需要一个单独的文件来编写Threejs的部分代码,这里我们命名为Threecore

在src下新建一个文件夹 Threecore,新建一个js文件 Threecore.js
此时的文件结构
在这里插入图片描述

使用ES6的Class来让逻辑性更强

这里我们先上改完后的代码

//ThreeCore.js
import {Scene, WebGLRenderer,Mesh,PerspectiveCamera, BoxGeometry, MeshBasicMaterial
} from "three";export default class ThreeCore {scene = new Scene();renderer = new WebGLRenderer({alpha:true});camera = new PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);/*** 构造函数,在new的时候会执行*/constructor() {this.init();this.addMesh();}/*** 初始化代码*/init(){this.camera.position.z = 5;this.renderer.setSize(window.innerWidth,window.innerHeight);this.renderer.domElement.style.position = "absolute";this.renderer.domElement.style.top = "0";this.renderer.domElement.style.left = "0";document.body.appendChild(this.renderer.domElement);}/*** 添加物体代码*/addMesh(){let geometry = new BoxGeometry(1,1,1);let material = new MeshBasicMaterial({color:0xff0000});this.mesh = new Mesh(geometry,material);this.scene.add(this.mesh);}/*** 重置画布大小*/resize = ()=>{this.renderer.setSize(window.innerWidth,window.innerHeight);this.camera.aspect = window.innerWidth/window.innerHeight;this.camera.updateProjectionMatrix();}/*** 渲染函数,这里本人为了干净和逻辑整洁,把requestAnimationFrame写到了main.js中*/render = ()=>{this.renderer.render(this.scene,this.camera);//编码习惯,使用前判定是否为null,这里的执行频率很高,可能会导致大量报错刷屏if(this.mesh){this.mesh.rotation.x += 0.01;this.mesh.rotation.y += 0.01;}}
}
//main.jsimport { createApp } from 'vue'
import App from './App.vue'
import ThreeCore from "@/ThreeCore/ThreeCore";createApp(App).mount('#app')let threeCore = new ThreeCore();render();function render() {threeCore.render();requestAnimationFrame(render);
}

Threejs尽量按需引入

在这里插入图片描述
Threejs本身文件很大,按需引入可以一定程度上降低打包出来的js文件的大小,所以我们在新的写法中做了按需引入

创建一个类

export default class ThreeCore{} 有了这一行之后,在main.js中,就可以

import ThreeCore from “@/ThreeCore/ThreeCore”;

并new出来

let threeCore = new ThreeCore();

new的时候,class系统会自动执行 constructor()函数,这个是类的功能

在类中,只能编写key和value这样的键值对,而不能直接编写代码,所以我们的操作代码都被归结在constructor函数中,并且用了**init()**来对逻辑进行区分,表示这一部分属于初始化阶段执行的代码,后续无需再次执行

扩展写法

当然,我们也可以把addMesh()写到main.js中,写法为:

threeCore.addMesh();

用new出来的实例,去调用它下面的函数addMesh()

本次代码执行顺序

//main.js
let threeCore = new ThreeCore()

//threecore.js
ThreeCore.constructor();
ThreeCore.init();
ThreeCore.addMesh();

//main.js
render();

扩展内容

添加orbitControls和辅助线

//ThreeCore
import {Scene, WebGLRenderer, Mesh,PerspectiveCamera, BoxGeometry, MeshBasicMaterial, GridHelper
} from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";export default class ThreeCore {scene = new Scene();renderer = new WebGLRenderer({alpha:true});camera = new PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);//可以对controls进行声明,不用第一时间赋值controls;/*** 构造函数,在new的时候会执行*/constructor() {this.init();this.addHelpers();this.addEvent();this.addMesh();}/*** 初始化代码*/init = ()=>{//调整相机位置this.camera.position.set(10,10,10);this.renderer.setSize(window.innerWidth,window.innerHeight);this.renderer.domElement.style.position = "absolute";this.renderer.domElement.style.top = "0";this.renderer.domElement.style.left = "0";document.body.appendChild(this.renderer.domElement);//个人习惯,喜欢把orbitControls写到init中this.controls = new OrbitControls(this.camera,this.renderer.domElement);this.controls.enableDamping = true;//开启阻尼效果}addHelpers(){let gridHelper = new GridHelper(10,10);this.scene.add(gridHelper);}addEvent = ()=>{window.addEventListener('resize',this.resize);}/*** 添加物体代码*/addMesh = ()=>{let geometry = new BoxGeometry(1,1,1);let material = new MeshBasicMaterial({color:0xff0000});this.mesh = new Mesh(geometry,material);this.scene.add(this.mesh);}/*** 重置画布大小*/resize = ()=>{this.renderer.setSize(window.innerWidth,window.innerHeight);this.camera.aspect = window.innerWidth/window.innerHeight;this.camera.updateProjectionMatrix();}/*** 渲染函数,这里本人为了干净和逻辑整洁,把requestAnimationFrame写到了main.js中*/render = ()=>{this.renderer.render(this.scene,this.camera);//编码习惯,使用前判定是否为null,这里的执行频率很高,可能会导致大量报错刷屏if(this.mesh){this.mesh.rotation.x += 0.01;this.mesh.rotation.y += 0.01;}if(this.controls){this.controls.update();}}}

两个新增的内容应该不用怎么解释了吧,页面效果

在这里插入图片描述
这里vue的dom跑到左边,是因为前面把定位改成了absolute,现在我们把位置调好
给app设定为100vw和100vh的宽高即可

/* app.vue的css部分 */
#app {width: 100vw;height: 100vh;font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;position: absolute;/*修改app的position*/z-index: 10;/*建议大于10*/
}

解决事件覆盖

在常规的3d项目中,ui的部分一般都是全屏,很容易把画布的事件覆盖了,所以我们在最顶层的dom层级中,设定它的事件为无,这样所有它下面的事件都会变成无
pointer-events:none;

这个样式代码,一般是这样,只看父级
你的父组件是none,则子组件也是none,
你的爷爷组件是none,父组件是auto,那么子组件也是auto
如果爷爷组件是auto,父组件是none,那么子组件也是none
所以用这个样式来处理顶级dom后,记得处理子级dom的pointerEvents

/* app.vue中的css的代码 */
#app {width: 100vw;height: 100vh;font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;position: absolute;/*修改app的position*/z-index: 10;/*建议大于10*/pointer-events: none;
}#app>*{pointer-events: auto;}

与Vue交互

在这里插入图片描述

//ThreeCore.js追加代码changeBoxColor = ()=>{//注意,这里必须要引入 Three里面的Color//不要引入错了,类型错误的话也会报错this.mesh.material.color = new Color(0xffffff * Math.random())}
//HelloWrold.vue重写代码
<template><div class="hello"><div @click="changeColor">变色</div></div>
</template><script>
export default {name: 'HelloWorld',props: {msg: String},methods:{changeColor(){console.log(this);window.threeCore.changeBoxColor();}}
}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {margin: 40px 0 0;
}
ul {list-style-type: none;padding: 0;
}
li {display: inline-block;margin: 0 10px;
}
a {color: #42b983;
}
</style>
//main.js重写代码
import { createApp } from 'vue'
import App from './App.vue'
import ThreeCore from "@/ThreeCore/ThreeCore";createApp(App).mount('#app')
window.threeCore = new ThreeCore();render();function render() {window.threeCore.render();requestAnimationFrame(render);
}

添加操作代码

我们只需要在ThreeCore下写一个函数即可
这里我们使用最简单的变色操作来演示

绑定ThreeCore到vue原型或window上

在vue2中,可以向原型上绑定一个对象,但是在vue3中,这个方案似乎不是很好用,本人并不是专门开发前端的,所以对vue3并不熟悉,所以绑定到原型的方式,就交给各位前端朋友了

最简单暴力的方式,就是绑定到 window上,虽然有被别人阅读源码和分析结构的风险,但是threejs本身高门槛,哪怕我代码放出来,你反编译了,你没有Threejs的基础也看不懂

window.threeCore = new ThreeCore();

在window上的对象,在你的程序的任何地方都可以直接调用,相当于一个全局形式的函数

在vue中调用函数

由于本人并不了解vue3,所以采用了比较旧的vue2的编码风格

    changeColor(){console.log(this);window.threeCore.changeBoxColor();//直接全局调用即可}

在这里插入图片描述

协同开发

通过上面的方式,其实不难发现,我们已经将vue部分和three部分彻底的拆开了,vue只需要负责搞dom,three只负责渲染画布即可
如果你们有两个以上的人,完全可以参考这样的开发模式,一个人纯写threejs,另一个人纯写vue,这样做完全不会有任何的冲突,写threejs部分的人,只需要提供几个方便调用的函数给另一个人,这样可以大幅提高开发效率和合作能力

全框架可行性说明

从代码中,我们是把threejs的部分的代码,绑定到window上,且主入口也在main.js这种分离模式下,就可以看出,同样的react,也适用于这样的开发模式
基本上只要是基于html + css + js的技术的,其实都可以使用这种方式来开发
本质上,这种开发方式是一种原生开发,而非什么基于vue啊,基于react,也不是必须要webpack,rolllup这种环境开发,只要你能拿到three.module.js,只要你能在入口文件编写自己的代码,就完全可以走这种开发模式

源码文件已上传,还有有不懂的问题可以在下方留言

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/867934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++模板元编程(三)——类型萃取

类型萃取(type_traits)是一种编译时技术&#xff0c;用于在编译期间获取和操作类型的信息&#xff0c;主要用于泛型编程以及在编译时做出决策。 文章目录 常见的类型萃取内部实现std::is_integral\<T\>std::enable_if_t<_Test, T> 应用 常见的类型萃取 在C11的<…

Java请求webService,IDEA生成客户端调用代码

Axis是Apache开放源代码组织的一个项目&#xff0c;全称为Apache Extensible Interaction System&#xff0c;简称Axis。它是一个基于Java的SOAP&#xff08;Simple Object Access Protocol&#xff0c;简单对象访问协议&#xff09;引擎&#xff0c;提供创建服务器端、客户端和…

游戏开发面试题3

unity如何判断子弹射击到敌人&#xff0c;如果子弹特别快怎么办 使用物理学碰撞检测。使用Unity的物理组件&#xff0c;如Rigidbody和Collider&#xff0c;将子弹和敌人都设置为有一定的物理碰撞属性&#xff0c;当子弹碰到敌人的时候&#xff0c;就会触发OnCollisionEnter()事…

游戏开发面试题2

详细说下堆排序。 堆排序是一种选择排序算法&#xff0c;它的基本思想是&#xff1a;将待排序序列构造成一个大顶堆&#xff0c;此时&#xff0c;整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换&#xff0c;此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个…

作业训练二编程题6. 小A的计算器

【问题描述】 以往的操作系统内部的数据表示都是二进制方式&#xff0c;小A新写了一个操作系统&#xff0c;系统内部的数据表示为26进制&#xff0c;其中0-25分别由a-z表示。 现在小A要在这个操作系统上实现一个计算器&#xff0c;这个计算器要能实现26进制数的加法运算…

Ajax与Fetch API在Web开发中的性能、用法与未来趋势比较

Ajax和Fetch都是JavaScript中用于从客户端向服务器发送请求以获取数据的技术&#xff0c;但它们之间存在一些显著的区别。以下是对这两种技术的详细比较&#xff1a; 一、技术基础与实现方式 Ajax&#xff1a; 基础&#xff1a;Ajax全称为Asynchronous JavaScript and XML&…

LabVIEW的Actor Framework (AF) 结构介绍

LabVIEW的Actor Framework (AF) 是一种高级架构&#xff0c;用于开发并发、可扩展和模块化的应用程序。通过面向对象编程&#xff08;OOP&#xff09;和消息传递机制&#xff0c;AF结构实现了高效的任务管理和数据处理。其主要特点包括并发执行、动态可扩展性和强大的错误处理能…

ROS——多个海龟追踪一个海龟实验

目标 通过键盘控制一个海龟&#xff08;领航龟&#xff09;的移动&#xff0c;其余生成的海龟通过监听实现追踪定期获取领航龟和其余龟的坐标信息&#xff0c;通过广播告知其余龟&#xff0c;进行相应移动其余龟负责监听 疑惑点&#xff08;已解决&#xff09; int main(int…

k8s 部署RuoYi-Vue-Plus之redis搭建

1.直接部署一个pod 需要挂载存储款, 可参考 之前文章设置 https://blog.csdn.net/weimeibuqieryu/article/details/140183843 2.部署yaml 先创建命名空间ruoyi, 有就不用创建了 kubectl create namespace ruoyi创建部署文件 redis-deploy.yaml kind: PersistentVolume api…

程序员学长 | 快速学会一个算法,xLSTM

本文来源公众号“程序员学长”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;快速学会一个算法&#xff0c;xLSTM 今天给大家分享一个超强的算法模型&#xff0c;xLSTM。 xLSTM&#xff08;Extended Long Short-Term Memory&…

Spring Boot的无缝衔接:深入解析与实践

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ &#x1f680;The begin&#x1f697;点点关注&#xff0c;收藏不迷路&#x1f6a9; 引言 在快速迭代的软件开发环境中&#xff0c;无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…

Electron开发 - 如何在主进程Main中让node-fetch使用系统代理

背景 开发过程中&#xff0c;用户设置的系统代理是不同的&#xff0c;比如公司内的服务器&#xff0c;所以就要动态地使用系统代理来访问&#xff0c;但是主进程默认为控制台级别的请求&#xff0c;不走系统代理&#xff0c;除非你指定系统代理配置&#xff0c;这个就就有了这…

轻松上手MYSQL:MYSQL事务隔离级别的奇幻之旅

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索MYSQL索引数据结构之旅✨ &#x1f44b; 大家好&#xff01;文本学习…

【话题】IT专业入门,高考假期预习指南

IT专业入门&#xff0c;高考假期预习指南 亲爱的高考学子们&#xff0c; 七月的阳光&#xff0c;如同你们的梦想&#xff0c;炽热而明亮。当你们手中的笔落下最后一道题的答案&#xff0c;那不仅仅是对过去十二年寒窗苦读的告别&#xff0c;更是对未知世界探索的启程号角。你们…

ExtruOnt——为工业 4.0 系统描述制造机械类型的本体

概述 论文地址 &#xff1a;https://arxiv.org/abs/2401.11848 原文地址&#xff1a;https://ai-scholar.tech/articles/ontology/ExtruOnt 在工业 4.0 应用场景中&#xff0c;以机器可解释代码提供的、语义丰富的制造机械描述可以得到有效利用。然而&#xff0c;目前显然还缺…

无锡哲讯:SAP赋能汽车零配件行业,加速企业数字化转型

汽车零配件行业作为汽车工业的重要支撑&#xff0c;正处在一个快速变化和高度竞争的市场环境中。随着科技的不断进步和消费者需求的日益多样化&#xff0c;如何通过高效的资源管理和生产流程优化&#xff0c;提升竞争力&#xff0c;已成为汽车零配件企业亟待解决的问题。无锡哲…

玩转springboot之springboot项目监测

项目监测 springboot中提供了actuator项目来进行监测和度量 基于springboot2.x版本 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency> actuator中提…

【数智化人物展】天云数据CEO雷涛:大模型连接数据库 为数智化提供高价值数据...

雷涛 本文由天云数据CEO雷涛投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级先锋人物》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 这几天&#xff0c;奥特曼讲SQL数据库和大模型结合起来会产生什么样的化学变化引起行业关注。为…

大模型备案全网最详细流程【附附件】

本文要点&#xff1a;大模型备案最详细说明&#xff0c;大模型备案条件有哪些&#xff0c;《算法安全自评估报告》模板&#xff0c;大模型算法备案&#xff0c;大模型上线备案&#xff0c;生成式人工智能(大语言模型)安全评估要点&#xff0c;网信办大模型备案。 大模型备案安…

Geoserver源码解读五 Catalog

系列文章目录 Geoserver源码解读一 环境搭建 Geoserver源码解读二 主入口 Geoserver源码解读三 GeoServerBasePage Geoserver源码解读四 REST服务 Geoserver源码解读五 Catalog 目录 系列文章目录 前言 一、定义 二、前置知识点 1.Spring 的 Bean 生命周期 ApplicationCon…