【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.xdnf.cn/news/1474922.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

《python程序语言设计》2018版第5章第53题利用turtle绘制sin和cos函数 sin蓝色,cos红色和52题类似

直接上题和代码 5.53 &#xff08;Turtle&#xff1a;绘制sin和cos函数&#xff09;编写程序绘制蓝色的sin函数和红色的cos函数。 代码和结果 turtle.speed(10) turtle.penup() # sin 用蓝色 turtle.color("blue") #这道题和上道题一样&#xff0c;先把turtle放到起始…

go-redis 封装事件-client封装模型、批量数据处理的导出器设计

一、redis-go的封装实践-client模型 // Copyright 2020 Lingfei Kong <colin404foxmail.com>. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file.package storageimport ("context&q…

程序员如何做好需求判断?

1. 导语 本文作为2024上半年核心思考之二。 通过他人经验传导、个人实践、广泛阅读书籍(方法论类、企业经营类、传记类、财务类&#xff0c;具体书单附文末)&#xff0c;学会基于更高阶的经营者视角来做好业务需求判断。本文思路如下&#xff1a; 首先&#xff0c;抛一个灵魂问…

60页论文参考:基于Java+SpringMvc+Vue技术的智慧校园系统设计与实现

详细查看地址&#xff1a; 基于JavaSpringMvcVue技术的智慧校园系统设计与实现-CSDN博客 基于JavaSpringMvcVue技术的智慧校园系统设计与实现 六、论文参考&#xff1a;

数据驱动的内容优化:Kompas.ai如何提升内容表现

在数字化营销时代&#xff0c;内容是企业与用户沟通的重要桥梁。然而&#xff0c;随着信息量的爆炸性增长&#xff0c;如何让内容在激烈的竞争中脱颖而出&#xff0c;成为每个营销人员面临的问题。数据驱动的内容优化策略&#xff0c;通过精准分析和科学决策&#xff0c;帮助品…

【Unity】简单举例UI合批,优化draw call

1. UGUI-Editor 首先引入UGUI-Editor插件 链接: https://pan.baidu.com/s/1PpxStvgRCDi9xjUr6j6nCQ?pwdm5ju 提取码: m5ju 或者直接去Github搜索UGUI-Editor 2. 没有UI时 3. 放几个UI看看效果 4. 选中Canvas&#xff0c;右键优化Batch 发现减少了3个&#xff0c;这是因为&…

[LoaderRunner] 关于LoaderRunner的基本使用

LoadRunner环境搭建 LoadRunner运行的环境参考以下文档&#xff1a;Docs 介绍LoadRunner LoadRunner是什么 LoadRunner是性能测试工具&#xff0c;对软件或者系统的性能进行评估 为什么使用LoadRunner LoadRunner具有以下的优势&#xff1a; LoadRunner相比于其他的测试工具…

【高中数学之函数】四种幂函数图线(二次、三次、开方、开立方)

【图像】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>UNASSIGNED</title><style type"text/css">.c…

数字传输系统的演变与应用

引言 在现代通信网络中&#xff0c;数字传输系统扮演着至关重要的角色。本文将用简单易懂的语言&#xff0c;向初学者介绍PCM速率体制、SONET/SDH以及光网络&#xff0c;让大家更好地理解这些技术。 2.5.1 PCM速率体制 数字传输系统简介 在早期电话网络中&#xff0c;用户电话…

[个人感悟] 消息队列应该考察哪些问题?

前言 消息队列. 不论是Java内部提供的LinkedBlockingQueue, 还是当下主流的中间件RabbitMQ, Kafka, RockMQ. 其本质上都是一个削峰填谷的工具. 我们都知道, 请求和流量都有可能瞬间很高, 或者很低. 所以, 很多时候, 我们需要请求存储起来, 或者使用异步的方式, 来匀速的处理过…

【C++】类和对象(中)--下篇

个人主页~ 类和对象上 类和对象中-上篇 类和对象 五、赋值运算符重载1、运算符重载2、赋值运算符重载3、前置和后置重载 六、const成员七、日期类的实现Date.hDate.cpptest.cpptest1测试结果test2测试结果test3测试结果test4测试结果test5测试结果test6测试结果test7测试结果 八…

基于Java+SpringMvc+Vue技术智慧校园系统设计与实现--60页及以上论文参考

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

8、Redis 的线程模型、I/O 模型和多线程

Redis 的线程模型、I/O 模型和多线程 1. Redis 的线程模型 Redis 以其高效的单线程模型著称&#xff0c;从设计之初&#xff0c;Redis 就选择了单线程模式&#xff0c;这在很大程度上简化了其内部实现和维护。单线程模式避免了多线程编程中常见的竞争条件和锁机制问题&#x…

Linux Bridge - Part 2

概览 在前一篇文章中&#xff0c;我描述了Linux 网桥&#xff08;bridge&#xff09;的配置&#xff0c;并展示了一个实验&#xff0c;其中使用Wireshark来分析流量。在本文中&#xff0c;我将讨论当创建一个网桥时会发生什么&#xff0c;以及Linux 网桥&#xff08;bridge&am…

C++初学者指南-4.诊断---valgrind

C初学者指南-4.诊断—Valgrind Valgrind&#xff08;内存错误检测工具&#xff09; 检测常见运行时错误 读/写释放的内存或不正确的堆栈区域使用未初始化的值不正确的内存释放&#xff0c;如双重释放滥用内存分配函数内存泄漏–非故意的内存消耗通常与程序逻辑缺陷有关&#xf…

Java版Flink使用指南——将消息写入到RabbitMQ的队列中

大纲 新建工程新增依赖 编码自动产生数据写入RabbitMQ 测试 在 《Java版Flink使用指南——从RabbitMQ中队列中接入消息流》一文中&#xff0c;我们介绍了如何使用Java在Flink中读取RabbitMQ中的数据&#xff0c;并将其写入日志中。本文将通过代码产生一些数据&#xff0c;然后…

Vine: 一种全新定义 Vue 函数式组件的解决方案

7月6日的 vue confg 大会上 ShenQingchuan 大佬介绍了他的 Vue Vine 项目&#xff0c; 一种全新定义 Vue 函数式组件的解决方案。 和 React 的函数式组件有异曲同工之妙&#xff0c;写起来直接起飞了。 让我们来快速体验一下 vine&#xff0c; 看看到底给我们带来了哪些惊喜吧…

分别通过LS和RML进行模型参数辨识matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 最小二乘法(LS)参数辨识 4.2 递归最大似然估计(RML)参数辨识 5.完整程序 1.程序功能描述 分别通过LS和RML进行模型参数辨识matlab仿真&#xff0c;仿真输出参数辨识的误差&#xff0c…

网络基础:BGP协议

BGP&#xff08;边界网关协议&#xff0c;Border Gateway Protocol&#xff09;是一种用于在不同自治系统&#xff08;Autonomous Systems&#xff0c;AS&#xff09;之间交换路由信息的路径向量协议。BGP是互联网的核心路由协议之一&#xff0c;负责管理和维护互联网范围内的路…

MySQL安全加固及等保测评

登录后复制 Mysql基础命令 create USER new_userlocalhost IDENTIFIED BY password; //创建用户 alter user root% identified with mysql_native_password by ********; //修改密码 rename user root% to root192.168.1.1; //重命名 flush privileges; …