【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,一经查实,立即删除!

相关文章

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

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

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…

程序员学长 | 快速学会一个算法,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…

轻松上手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;目前显然还缺…

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

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

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

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

中国石油大学(华东)24计算机考研数据速览,计科学硕复试线288分!

中国石油大学&#xff08;华东&#xff09;计算机与通信工程学院是中国石油大学(华东)十三个教学院部之一&#xff0c;其前身是创建于1984年的计算机科学系&#xff0c;2001年撤系建院。伴随着学校50多年的风雨历程&#xff0c;计算机与通信工程学院也已经有了20多年的发展历史…

5.pwn Linux的延迟绑定机制

动态链接库 我们程序开发过程中都会用到系统函数&#xff0c;比如read&#xff0c;write&#xff0c;open等等 这些系统函数不需要我们实现&#xff0c;因为系统已经帮你完成这些工作&#xff0c;只需要调用即可&#xff0c;存放这些函数的库文件就是动态链接库。 通常情况下&…

[激光原理与应用-100]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 4 - 3C电池行业应用 - 不同的电池类型、焊接方式类型

目录 前言&#xff1a; 一、激光在3C行业的应用概述 1.1 概述 1.2 激光焊接在3C-电池行业的应用 1.3 动力电池的激光焊接工艺 1.3.1 概述 1.3.2 动力电池常见的焊接应用 1.电池壳体与盖板焊接 2.电池防爆阀密封 焊接 二、不同的电池的外形 2.1 软包锂电池 2.1.1 概述…

《机器学习》读书笔记:总结“第4章 决策树”中的概念

&#x1f4a0;决策树 基于树结构进行决策。 一棵决策树包括&#xff1a; 一个 根节点&#xff08;起点&#xff09;若干 叶节点&#xff08;没有下游节点的节点&#xff09;若干 内部节点(分支节点) 即&#xff1a; #mermaid-svg-Mxe3d0kNg29PM2n8 {font-family:"treb…

Open3D 删除点云中重叠的点(方法二)

目录 一、概述 1.1原理 1.2应用 二、代码实现 三、实现效果 3.1原始点云 3.2处理后点云 3.3数据对比 一、概述 在点云处理中&#xff0c;重叠点&#xff08;即重复点&#xff09;可能会对数据分析和处理的结果产生负面影响。因此&#xff0c;删除重叠点是点云预处理中常…

NextJs - SSR渲染解决antd首屏加载CSS样式的闪烁问题

NextJs - SSR渲染解决antd首屏加载CSS样式的闪烁问题 闪烁现状解决方案 闪烁现状 我们写一个非常简单的页面&#xff1a; import { Button } from antdexport default async function Page() {return <><Button typeprimary>AAA</Button></> }NextJs…

《昇思25天学习打卡营第14天|onereal》

第14天学习内容如下&#xff1a; Diffusion扩散模型 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻译迁移而来&#xff0c;同时参考了由浅入深了解Diffusion Model一文。 本教程在Jupyter Notebook上成功运行。如您下载本文档为Python文件&#xff0c…

张量分解(1)——初探张量

&#x1f345; 写在前面 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;这里是hyk写算法了吗&#xff0c;一枚致力于学习算法和人工智能领域的小菜鸟。 &#x1f50e;个人主页&#xff1a;主页链接&#xff08;欢迎各位大佬光临指导&#xff09; ⭐️近…

SpEL表达式相关知识点

SpEL表达式 知识点 Spel概述 Spring 表达式&#xff0c;即 Spring Expression Language&#xff0c;简称 SpEL。 那么是什么SpEL表达式呢&#xff1f; SpEL (Spring Expression Language) 是一种在Spring框架中用于处理表达式的语言。SpEL中的表达式可以支持调用bean的方法…

IntelliJ IDEA菜单不见了设置找回方法

通过CtrAltS键按出设置 找到View,然后自定义一个快捷键,然后保存 使用自定义快捷键弹出改界面,点击Main Menu即可