弄项目,总想搞点3D的东西,到项目里面,即能提高项目的B格,又能拿去和领导吹牛皮。
但项目开发,又一般是react、vue那种模块化开发。threejs开发,网上找下例子又全是基础的html、js。那炸糕?
1、文件放入public内,iframe引入、a标签跳转
之前有次面试的时候,面试官一个问题让我印象深刻。他看我项目中使用react、threejs,就问:“你是怎么在react中使用threejs的?为啥要在react使用three?使用啥react特性优化了threejs性能?”
我giao!好像真的是呀!为啥要这样做?!!!
threejs 是使用 requestAnimationFrame 去 renderer.render( scene, camera );(重新渲染canvas)。而react 是使用 state 状态的更新,重新render dom节点!
threejs:"老资,不需要react你的更新!我的canvas自己动。"
这里就有了第一种方式。我称之为:分居2地式!
react项目一般有pubilc放静态资源的地方,通过绝对路径能访问。所以,我们可以把threejs文件,放到public内,然后react组件,通过iframe啥的进行引入,或者放a标签链接。
优缺点
优点:简单粗暴。网上找的例子直接往项目里面怼,甚至都不用改啥的。(也不用考虑react对three的影响)
这种,其实和分成2个项目没啥区别。
缺点:交互比较麻烦,如果three模块内和外面模块有啥交互的,,,很麻烦。(只能通过iframe的postmessage啥的进行交互)
总结:如果three模块比较独立,和其他模块基本没关系,同时又不想起另外的项目。可以使用这种方式,进行快速开发。
2、模块引入threejs等,组件内开发
继续上面的问题:为啥要在react使用three?使用啥react特性优化了threejs性能?
他喵的,被面试官,“性能”2个词一绕,我脑袋只思考性能去了····
我react 内开发,three模块,肯定是想使用模块化来开发 three模块阿!
举个实际的应用场景,我有一堆设备,每个设备详情,都需要展示 这个设备的3D模型。我如果有这个3D模型的three组件,我只需要传不同的参数就可以了?如果涉及到3D模块整体背景色啥的调整。我只需要调整three组件就好了!
import * as THREE from 'three';
使用babel-plugin-import等,引入BSP等模块。
umi 内可以使用:externals 参数进行配置
然后开发组件就好了。(举个例子)
interface MyProps {
style?: Record<string, any>;
[key: string]: any;
}
let timeNow = new Date().valueOf();
const values = { tep: '40.0', sec: '20.0' };
const meshcolor = 0xffffff;
let renderer: any,
camera: any,
scene: any,
light: any,
controls: any,
isAutoRotate: boolean,
cube: any;
export default (props: MyProps) => {
const { style, ...pros } = props;
const [btnRender, setBtnRender] = useState<boolean>(false);
const renderRef = useRef<any>();
// 初始化render
const threeInit = (curDom: any) => {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(curDom.clientWidth, curDom.clientHeight);
renderer.setClearColor(0xeeeeee);
renderer.shadowMap.enabled = true;
// curDom.innerHTML = null;
curDom.appendChild(renderer.domElement);
};
// 初始化相机
const initCamera = (curDom: any) => {
camera = new THREE.PerspectiveCamera(45, curDom.clientWidth / curDom.clientHeight, 0.1, 300);
camera.position.set(0, 15, 40);
};
// 初始化场景
const initScene = () => {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x39609b);
// scene.fog = new THREE.Fog( 0xa0a0a0, 5, 250 );
};
// 初始化光线
const initLight = (myScene: any) => {
light = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
myScene.add(light);
};
// 初始化控制器
const initControls = (myCamera: any) => {
controls = new OrbitControls(myCamera, renderer.domElement);
//设置控制器的中心点
controls.target.set(0, 10, 0);
// 如果使用animate方法时,将此函数删除
//controls.addEventListener( 'change', render );
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = false;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度
// 阻尼系数
controls.dampingFactor = 0.1;
controls.minPolarAngle = Math.PI / 12;
controls.maxPolarAngle = (Math.PI * 19) / 40;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
//设置相机距离原点的最远距离
controls.minDistance = 10;
//设置相机距离原点的最远距离
controls.maxDistance = 200;
//是否开启右键拖拽
controls.enablePan = true;
controls.update();
};
// 获取带数据的canvas
const getTextCanvas = async ({ tep, sec }: any) => {
const time = moment().format('HH:mm:ss');
const width = 200,
height = 300;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
return new Promise((resole) => {
if (ctx) {
const img = new Image();
img.src = collectorImg;
//图片加载完后,将其显示在canvas中
img.onload = () => {
ctx.drawImage(img, 0, 0, width, height);
ctx.font = 30 + 'px " bold';
ctx.fillStyle = '#333';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(tep, (width * 11) / 24, (height * 10) / 40);
ctx.fillText(sec, (width * 11) / 24, (height * 20) / 40);
ctx.font = 10 + 'px " bold';
ctx.fillText(time, (width * 16.5) / 24, (height * 26.6) / 40);
resole(canvas);
};
}
});
};
// 改变材质种类
const changeMaterial = async (myScene: any) => {
const canvas: any = await getTextCanvas({ tep: 40, sec: 20 });
if (canvas) {
const texture = new THREE.CanvasTexture(canvas);
const materials = [
new THREE.MeshLambertMaterial({ color: meshcolor, opacity: 1, transparent: true }),
new THREE.MeshLambertMaterial({ color: meshcolor, opacity: 1, transparent: true }),
new THREE.MeshLambertMaterial({ color: meshcolor, opacity: 1, transparent: true }),
new THREE.MeshLambertMaterial({ color: meshcolor, opacity: 1, transparent: true }),
new THREE.MeshLambertMaterial({
color: meshcolor,
opacity: 1,
transparent: true,
map: texture,
}),
new THREE.MeshLambertMaterial({ color: meshcolor, opacity: 1, transparent: true }),
];
const geometry = new THREE.BoxGeometry(20, 20, 5);
cube = new THREE.Mesh(geometry, materials);
cube.position.set(0, 10, 0);
myScene.add(cube);
}
};
// 初始化物体
const initModel = (myScene: any) => {
// 添加collector
changeMaterial(myScene);
};
// 更新value数据
const changeValues = () => {
values.tep = (39 + Math.random() * 2).toFixed(1);
values.sec = (19 + Math.random() * 2).toFixed(1);
};
const updateData = async () => {
if (new Date().valueOf() - timeNow > 5000) {
timeNow = new Date().valueOf();
changeValues();
}
const canvas: any = await getTextCanvas(values);
if (canvas && cube) {
cube.material[4].map = new THREE.CanvasTexture(canvas);
cube.material.map.needsUpdate = true;
}
};
const render = () => {
if (renderer && scene && camera) {
renderer.render(scene, camera);
}
};
const animate = () => {
//更新控制器
render();
if (controls && isAutoRotate) {
controls.update();
}
requestAnimationFrame(animate);
};
useEffect(() => {
if (renderRef && renderRef.current) {
threeInit(renderRef.current);
initCamera(renderRef.current);
initScene();
animate();
}
const timer = setInterval(() => {
updateData();
}, 1000);
return () => {
cancelAnimationFrame(1);
clearInterval(timer);
};
}, [renderRef]);
useEffect(() => {
if (scene) {
initLight(scene);
initModel(scene);
}
}, [scene]);
useEffect(() => {
if (camera) {
initControls(camera);
}
}, [camera]);
return (
<div
ref={renderRef}
style={{ width: '300px', height: '200px', position: 'relative', ...style }}
{...pros}
>
<div className={styles.btns}>
<div
className={styles.btn}
onClick={() => {
isAutoRotate = !isAutoRotate;
setBtnRender(isAutoRotate);
}}
>
{btnRender ? '自动旋转' : '旋转关闭'}
</div>
</div>
</div>
);
};
优缺点:
优点:模块化开发,交互、传值啥的方便。
缺点:
1、引入相关模块的时候,比较恶心(挺难弄的)。
2、基础代码啥的还是得自己慢慢封装啥的。
3、渲染问题:react state值不能控制 three更新;(当然,如果你把渲染直接交给react,当我没说)
3、@react-three/fiber
自己搞太麻烦了,有没有现成的?肯定有阿,网上全是大神!!(又是鉴定自己辣鸡的一天)
github地址:github.com/pmndrs/reac…
demo:codesandbox.io/s/rrppl0y8l…
npm install three @react-three/fiber
import ReactDOM from 'react-dom'
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
function Box(props) {
// This reference gives us direct access to the THREE.Mesh object
const ref = useRef()
// Hold state for hovered and clicked events
const [hovered, hover] = useState(false)
const [clicked, click] = useState(false)
// Subscribe this component to the render-loop, rotate the mesh every frame
useFrame((state, delta) => (ref.current.rotation.x += 0.01))
// Return the view, these are regular Threejs elements expressed in JSX
return (
<mesh
{...props}
ref={ref}
scale={clicked ? 1.5 : 1}
onClick={(event) => click(!clicked)}
onPointerOver={(event) => hover(true)}
onPointerOut={(event) => hover(false)}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
ReactDOM.render(
<Canvas>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>,
document.getElementById('root'),
)
这没啥好说的,光是看到这个简洁的代码,就一个字:“爽!”
我简单使用下,还是挺香的。
但是,恶心的事来了,我的业务使用的架构是umi,它会统一对props.children做处理。它对react-three/fiber,解析处理会失败!!!
在座的各位大佬,有相关的解决思路没有? help
结束语
新年快乐
其他文章:
自定义编辑3D房间工具(二) 添加门等新模块、模块选中及删除