如何在没有 react-three-fiber 的情况下通过 react 访问纯 threejs 元素?

How can I access pure threejs elements with react without react-three-fiber?

基本上,我想访问我的 3D 模型元素,并想从另一个组件控制相机和其他东西,如网格、动画等。我正在使用纯 threeJs 并为此做出反应。 我在 react App class 的 componentDidMount 部分添加了纯 threejs 代码,我想控制来自另一个名为 CollapseButton 的组件的 componentDidMount 的相机部分。如何从 CollapseButton 访问那些相机、场景、材料?此外,当我单击 CollapseButton 中的按钮时,我想使用 componentDidMount 中声明的 threeJs 部分执行特定任务。

简而言之:从 CollapseButton 我想点击一个按钮并在 ComponentDidMount 中声明的纯 threeJs 部分上执行特定任务。单击 CollapseButton 中定义的按钮 > 调用函数或在 componentDidmount/threejs 部分

上执行某些操作

这是我的 App.js:

// ... App.js
import React from "react";
import ReactDOM from "react-dom";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import './App.css';
import CollapseButton from './CollapseButton'
class App extends React.Component {
  componentDidMount() {
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    this.mount.appendChild(renderer.domElement);

    const sizes = {
      width: window.innerWidth,
      height: window.innerHeight,
    };

    //Creating scene
    var scene = new THREE.Scene();

    //creating camera
    var camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    camera.position.x = 100;
    camera.position.y = 20;
    camera.position.z = 2;
    camera.lookAt(0, -10, 0);

    window.addEventListener("resize", () => {
      // Update sizes
      sizes.width = window.innerWidth;
      sizes.height = window.innerHeight;

      // Update camera
      camera.aspect = sizes.width / sizes.height;
      camera.updateProjectionMatrix();

      // Update renderer
      renderer.setSize(sizes.width, sizes.height);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    });

    //loader
    let mesh;
    const gltfLoader = new GLTFLoader();
    gltfLoader.load("LeftBankUnitBoxes.glb", handle_load);
    const texture = new THREE.TextureLoader();
    const grass = texture.load("/textures/Grass.PNG");
    function handle_load(gltf) {
      mesh = gltf.scene;
      mesh.traverse((child) => {
        // if (child.material && child.material.name === "M_UnitBox_349") {
        //     child.material.map=grass

        //     // console.log(child.material)

        //   }
        if (child.material) {
          child.material.map = grass;
          // child.material.metalness=0.8
          // child.material.roughness=0.2
          // child.material = new THREE.MeshStandardMaterial({
          //   map: grass,
          //   metalness:1,
          //   roughness:10,
          //   metalnessMap:asphalt,
          //   envMap:cubeTextureLoader
          //   // transparent:true,
          //   // opacity: 0.2,
          // });
          // console.log(child.material)
        }
      });

      mesh.position.y = -50;
      scene.add(mesh);
    }

    function call(){
      console.log("Calling log")
    }

    //creating controls
    const orbit = new OrbitControls(camera, renderer.domElement);
    orbit.maxPolarAngle = Math.PI / 2;
    // orbit.autoRotate = true;
    orbit.update();

    //creating lights

    var Dirlight = new THREE.DirectionalLight("#ffffff", 1);
    var ambient = new THREE.AmbientLight("#ffffff", 1);
    Dirlight.position.set(-50, 20, 0).normalize();

    Dirlight.castShadow = true;
    scene.add(Dirlight);

    scene.add(ambient);

    //animate
    var animate = function () {
      requestAnimationFrame(animate);

      renderer.render(scene, camera);
      orbit.update();
    };
    animate();
  }

  render() {
    return <div ref={(ref) => (this.mount = ref)} />;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <>
    <App />
    <div className="Collapse">
      <CollapseButton call={call}/>
    </div>
  </>,
  rootElement
);
export default App;

这是我的折叠按钮:

import React, { useState } from 'react'
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';

export default function Collapse({call}) {
    const [icon,setIcon]=useState(false)

    const show=()=>{
        //handleRender()
        call()
        setIcon(!icon)
    }
  return (
    <>
        <Button style={{maxWidth: '30px', maxHeight: '30px', minWidth: '30px', minHeight: '30px'}} onClick={show}>
            {icon?<ArrowBackIosIcon style={{color:'white',fontSize:24}}/>:<ArrowForwardIosIcon  style={{color:'white',fontSize:24,fontWeight:'bold'}} />}
        </Button>
        
    </>
  )
}

我试图将一个函数从 ComponentDidMount 传递到 CollapseButton,当我从 CollapseButton 中单击一个按钮时可能会调用它,但找不到任何方法来传递该函数。

如果您打算在多个组件中使用它,您可以将对 threeJS 组件的引用存储在一个 React ref and expose it to your collapse button as a prop (or by a React context 中)

作为道具:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.objectRef = React.createRef();
  }

  componentDidMount() {
    // ...three.js code
    this.objectRef.current = myObject;
  }

  render() {
    return (
     <>
       <div ref={(ref) => (this.mount = ref)} />
       <div className="Collapse">
         <CollapseButton call={call} object={objectRef} />
       </div>
     </>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App />,
  rootElement
);
export default App;
export default function Collapse({ call, object }) {
  // ...

    const show=()=>{
        const myObject = object.current; // Get the object
        // Perform the task here

        call()
        setIcon(!icon)
    }

  // ...
}

使用上下文:

const ThreeContext = React.createContext({});

class App extends React.Component {
  constructor(props) {
    super(props);
    this.objectRef = React.createRef();
  }

  componentDidMount() {
    // ...three.js code
    this.objectRef.current = myObject;
  }

  render() {
    return (
     <ThreeContext.Provider value={{ object: this.objectRef }}>
       <div ref={(ref) => (this.mount = ref)} />
       <div className="Collapse">
         <CollapseButton call={call} />
       </div>
     </ThreeContext.Provider>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <App />,
  rootElement
);
export default App;
export default function Collapse({ call }) {
  // ...

    const show=()=>{
        const myObject = this.context.object?.current; // Get the object
        // Perform the task here

        call()
        setIcon(!icon)
    }

  // ...
}

您可以将 three.js 相关代码移动到不同的文件中并在需要的地方导入

T3.js

// Creating renderer
const renderer = new THREE.WebGLRenderer();
// Creating scene
const scene = new THREE.Scene();
// Creating camera
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const animate = function () {
  // requestAnimationFrame(this.animate);

  renderer.render(scene, camera);
  this.orbit.update();
};

export default {
  renderer,
  scene,
  camera,
  texture: new THREE.TextureLoader(),
  gltfLoader: new GLTFLoader(),
  gltfHandler: function (gltf) {
    console.log(this);
    const grass = this.texture.load('/textures/Grass.PNG');
    let mesh = gltf.scene;
    mesh.traverse((child) => {
      // if (child.material && child.material.name === "M_UnitBox_349") {
      //     child.material.map=grass

      //     // console.log(child.material)

      //   }
      if (child.material) {
        child.material.map = grass;
        // child.material.metalness=0.8
        // child.material.roughness=0.2
        // child.material = new THREE.MeshStandardMaterial({
        //   map: grass,
        //   metalness:1,
        //   roughness:10,
        //   metalnessMap:asphalt,
        //   envMap:cubeTextureLoader
        //   // transparent:true,
        //   // opacity: 0.2,
        // });
        // console.log(child.material)
      }
    });

    mesh.position.y = -50;
    scene.add(mesh);
  },
  orbit: new OrbitControls(camera, renderer.domElement),
  sizes: {
    width: window.innerWidth,
    height: window.innerHeight,
  },
  init: function () {
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.camera.position.x = 100;
    this.camera.position.y = 20;
    this.camera.position.z = 2;
    this.camera.lookAt(0, -10, 0);

    window.addEventListener('resize', () => {
      // Update sizes
      this.sizes.width = window.innerWidth;
      this.sizes.height = window.innerHeight;

      // Update camera
      this.camera.aspect = this.sizes.width / this.sizes.height;
      this.camera.updateProjectionMatrix();

      // Update renderer
      this.renderer.setSize(this.sizes.width, this.sizes.height);
      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    });

    //loader
    this.gltfLoader.load('LeftBankUnitBoxes.glb', this.gltfHandler);

    //creating controls
    this.orbit.maxPolarAngle = Math.PI / 2;
    // orbit.autoRotate = true;
    this.orbit.update();

    //creating lights
    var Dirlight = new THREE.DirectionalLight('#ffffff', 1);
    var ambient = new THREE.AmbientLight('#ffffff', 1);
    Dirlight.position.set(-50, 20, 0).normalize();

    Dirlight.castShadow = true;
    this.scene.add(Dirlight);
    this.scene.add(ambient);
  },
  animate,
  call: function () {
    console.log('Calling log');
  },
};

App.js

import React from 'react';
import './style.css';

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import T3 from './T3'

class App extends React.Component {
  componentDidMount() {
    T3.init();
    this.mount.appendChild(T3.renderer.domElement);
    T3.animate();
  }

  render() {
    return <div ref={(ref) => (this.mount = ref)} />;
  }
}

Collapse.js

import React, { useState } from 'react'
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import { Button } from '@mui/material';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import T3 from './T3'

function Collapse({ call }) {
  const [icon, setIcon] = useState(false);

  const show = () => {
    //handleRender()
    T3.call();
    setIcon(!icon);
  };
  return (
    <>
      <Button
        style={{
          maxWidth: '30px',
          maxHeight: '30px',
          minWidth: '30px',
          minHeight: '30px',
        }}
        onClick={show}
      >
        {icon ? (
          <ArrowBackIosIcon style={{ color: 'white', fontSize: 24 }} />
        ) : (
          <ArrowForwardIosIcon
            style={{ color: 'white', fontSize: 24, fontWeight: 'bold' }}
          />
        )}
      </Button>
    </>
  );
}