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

news2025/3/2 2:39:29

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.js

import { 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.coloradmin.cn/o/1905536.html

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

相关文章

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

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

AI大模型的智能心脏:向量数据库的崛起

在人工智能的飞速发展中&#xff0c;一个关键技术正悄然成为AI大模型的智能心脏——向量数据库。它不仅是数据存储和管理的革命性工具&#xff0c;更是AI技术突破的核心。随着AI大模型在各个领域的广泛应用&#xff0c;向量数据库的重要性日益凸显。 01 技术突破&#xff1a;向…

水箱高低水位浮球液位开关工作原理

工作原理 水箱高低水位浮球液位开关是一种利用浮球随液位升降来实现液位控制的设备。其基本原理是浮球在液体的浮力作用下上下浮动&#xff0c;通过磁性作用驱动与之相连的磁簧开关的开合&#xff0c;从而实现液位的高低控制和报警。当液位升高时&#xff0c;浮球上浮&#xf…

Jmeter实现接口自动化

自动化测试理论知识 什么是自动化测试&#xff1f; 让程序或工具代替人为执行测试用例什么样的项目适合做自动化&#xff1f; 1、项目周期长 --多长算长&#xff1f;&#xff08;自己公司运营项目&#xff09; 2、需求稳定&#xff08;更多具体功能/模块&#xff09; 3、需要…

LabVIEW机器视觉系统中的图像畸变、校准和矫正

在机器视觉应用中&#xff0c;图像畸变、校准和矫正是确保图像准确性的关键步骤。LabVIEW作为一种强大的图像处理和分析工具&#xff0c;提供了一系列功能来处理这些问题。以下是对图像畸变、校准和矫正的详细介绍。 图像畸变 图像畸变 是指由于摄像镜头的光学特性或拍摄角度问…

昇思25天学习打卡营第3天|MindSpore张量

# 打卡 目录 # 打卡 类 涉及知识点 1. 创建张量的4种方式 运行例子 2. 张量属性和索引 运行例子 3. 张量运算 运行例子 4. Tensor 与 Numpy 转换 5. 稀疏张量&#xff1a;CSR和COO CSRTensor 运行例子 COOTensor 运行例子 RowTensor 类 import mindspore from…

CMD命令详细介绍 | 超详细版本!

文章目录 启动cmd命令用户启动使用管理员的账号启动 文件夹命令网络命令其他常用命令介绍常用快捷方式程序员相关命令 本文参考了博客园一篇帖子&#xff0c;ULR&#xff1a;cmd常用命令介绍(可收藏) - Mrwhite86 - 博客园 (cnblogs.com) CMD是Windows操作系统自带的命令行解释…

【Linux】打包命令——tar

打包和压缩 虽然打包和压缩都涉及将多个文件组合成单个实体&#xff0c;但它们之间存在重要差异。 打包和压缩的区别&#xff1a; 打包是将多个文件或目录组合在一起&#xff0c;但不对其进行压缩。这意味着打包后的文件大小可能与原始文件相同或更大。此外&#xff0c;打包…

【工具分享】Gophish

文章目录 Gophish安装方式功能简介 Gophish Gophish 是一个开源的网络钓鱼框架&#xff0c;它被设计用于模拟真实世界的钓鱼攻击&#xff0c;以帮助企业和渗透测试人员测试和评估他们的网络钓鱼风险。Gophish 旨在使行业级的网络钓鱼培训对每个人都是可获取的&#xff0c;它易…

文件存储的方法一

文章目录 概念介绍实现方法示例代码 我们在上一章回中介绍了"如何实现本地存储"相关的内容&#xff0c;本章回中将介绍如何实现文件存储.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 我们在上一章回中介绍的本地存储只能存储dart语言中基本类型的数值…

鲨疯了,免费的Viva又能画图,又能做视频,又能......

theme: smartblue 点赞 关注 收藏 学会了 本文简介 众所周知&#xff0c;Midjourney擅长画画&#xff0c;Runway擅长做视频(Sora也说它可以做)。 Viva&#xff1a;我不是针对你&#xff0c;我只想说在座的各位都是乐色&#xff5e; 注意&#xff0c;它叫Viva&#xff0c;不是…

Java引用的4种类型:强、软、弱、虚

在Java中&#xff0c;引用的概念不仅限于强引用&#xff0c;还包括软引用、弱引用和虚引用&#xff08;也称为幻影引用&#xff09;。这些引用类型主要用于不同的内存管理策略&#xff0c;尤其是在垃圾收集过程中。以下是对这四种引用类型的详细解释&#xff1a; 1. 强引用&am…

GD32实战篇-双向数控BUCK-BOOST-BUCK降压理论基础

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 向上代码兼容GD32F450ZGT6中使用 后续项目主要在下面该专栏中发布&#xff1a; https://blog.csdn.net/qq_62316532/category_12608431.html?spm1001.2014.3001.5482 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转…

论文解析——FTRANS: Energy-Efficient Acceleration of Transformers using FPGA

作者及发刊详情 Li B , Pandey S , Fang H ,et al.FTRANS: energy-efficient acceleration of transformers using FPGA[J].ACM, 2020.DOI:10.1145/3370748.3406567. 摘要 正文 主要工作贡献 与CPU和GPU在执行Transformer和RoBERTa相比&#xff0c;提出的FTRANS框架获得了…

计算云服务2

第二章 裸金属服务器 什么是裸金属服务器(BMS) 裸金属服务器(Bare Metal Server&#xff0c;BMS)是一款兼具虚拟机弹性和物理机性能的计算类服务为用户以及相关企业提供专属的云上物理服务器&#xff0c;为核心数据库、关键应用系统、高性能计算、大数据等业务提供卓越的计算…

买的Google账号登录,修改辅助邮箱收不到验证码?可能是个简单的错误

这篇文章分享一个案例&#xff0c;购买了谷歌账号以后如何修改辅助邮箱&#xff0c;修改辅助邮箱的一些要点&#xff0c;以及常见的一个错误。 一、案例回放 这个朋友昨天在我的一个视频下面留言说买了谷歌账号以后&#xff0c;想修改辅助邮箱地址&#xff0c;但是输入了辅助…

µCOS-III 任务同步机制-任务信号量

1. 什么是任务信号量 任务信号量是一种用于任务间同步和通信的计数器&#xff0c;通常用于解决任务间的竞争条件和资源共享问题。在C/OS-III中&#xff0c;任务信号量提供了二进制信号量和计数信号量两种类型&#xff1a; 二进制信号量&#xff1a;只能取值0或1&#xff0c;适…

VSCode神仙插件——Codeium (AI编程助手)

1、安装&登录插件 安装过程中会让你登录Codeium账户&#xff0c;可以通过Google账户登录&#xff0c;或者可以注册一个Codeium账户&#xff08;如果没有弹出让你登录账户的界面&#xff0c;可以等安装结束后在右下角找到登录的地方&#xff09; 右下角显示如下图所示&#…

大模型面试题目

1.为什么需要做位置编码 位置编码&#xff08;Positional Encoding&#xff09;在变换器&#xff08;Transformer&#xff09;模型中非常重要&#xff0c;因为变换器架构本身没有内置的顺序信息。变换器使用的是自注意力机制&#xff0c;它能够捕捉输入序列中所有词之间的相关性…

opencv_C++学习笔记(入门30讲)

文章目录 1.配置开发环境2.图像读取与显示3.图像色彩空间转换4.图像对象的创建与赋值5.图像像素的读写操作6.图像像素的算数操作7.滚动条-调整图像亮度8.滚动条-调整对比度和亮度9.键盘响应操作10.图像像素的逻辑操作11.图像的通道分离和合并12.图像色彩空间转换13.图像的像素值…