按需渲染 Three.js 多画布(带类实现)

时间:2021-01-25 16:59:48

标签: javascript three.js

我的情况是这样的:我有一个包含多个资产的资产清单,我希望每当用户将鼠标悬停在它们之上时就使用 OrbitController 开始渲染(我更喜欢轨迹球,但我知道这是不可能的,因为一个错误)。重点是类实例的“this”变量不能传入类的render方法中:

import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/examples/jsm/controls/OrbitControls.js';



class Main {

  constructor(canvasId)
  {
    this.canvas = document.querySelector(canvasId);

    this.renderer = new THREE.WebGLRenderer({canvas: this.canvas});

    this.fov = 75;
    this.aspect = 2;  // the canvas default
    this.near = 0.1;
    this.far = 5;
    this.camera = new THREE.PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
    this.camera.position.z = 2;

    this.controls = new OrbitControls(this.camera, this.canvas);
    this.controls.target.set(0, 0, 0);
    this.controls.update();

    this.scene = new THREE.Scene();


    this.color = 0xFFFFFF;
    this.intensity = 1;
    this.light = new THREE.DirectionalLight(this.color, this.intensity);
    this.light.position.set(-1, 2, 4);
    this.scene.add(this.light);


    this.boxWidth = 1;
    this.boxHeight = 1;
    this.boxDepth = 1;
    this.geometry = new THREE.BoxGeometry(this.boxWidth, this.boxHeight, this.boxDepth);
  }

  makeInstance(geometry, color, x){

    let material = new THREE.MeshPhongMaterial(color);

    let cube = new THREE.Mesh(geometry, material);
    this.scene.add(cube);
    cube.position.x = x;

    return cube;
  }

   
  resizeRendererToDisplaySize(renderer) {
    this.canvas = this.renderer.domElement;
    let width = this.canvas.clientWidth;
    let height = this.canvas.clientHeight;
    let needResize = this.canvas.width !== width || this.canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }

    return needResize;
  }

  render() {
    
    // this -> control object or window whereas it should be the class instance   
    if (this.resizeRendererToDisplaySize(this.renderer)) {
      this.canvas = this.renderer.domElement;
      this.camera.aspect = this.canvas.clientWidth / this.canvas.clientHeight;
      this.camera.updateProjectionMatrix();
    }
    
    this.renderer.render(this.scene, this.camera);
    
  }

  starter() {

    this.makeInstance(this.geometry, 0x44aa88,  0);
    this.makeInstance(this.geometry, 0x8844aa, -2);
    this.makeInstance(this.geometry, 0xaa8844,  2);

    this.render();

    this.controls.addEventListener('change', this.render);

    window.addEventListener('resize', this.render);

    // note: this is a workaround for an OrbitControls issue
    // in an iframe. Will remove once the issue is fixed in
    // three.js

    window.addEventListener('mousedown', (e) => {
      e.preventDefault();
      window.focus();
    });

    window.addEventListener('keydown', (e) => {
      e.preventDefault();
    });
  }
}



let main = new Main('#a');
main.starter();

我唯一能做的(但不正确)是用“main”实例替换渲染方法中的“this”

render() {
        
           
    if (main.resizeRendererToDisplaySize(this.renderer)) {
      main.canvas = main.renderer.domElement;
      main.camera.aspect = main.canvas.clientWidth / main.canvas.clientHeight;
      main.camera.updateProjectionMatrix();
    }
    
    main.renderer.render(main.scene, main.camera);
    
}

但是,我以这种方式杀死了该类,并且无法生成其他实例,例如主2 我已经生成了一个代码笔:https://codepen.io/jimver04/pen/XWjLPLo

2 个答案:

答案 0 :(得分:2)

您正在向事件处理程序提供 this.renderthis.render 在技术上是指向原型函数 (Main.prototype.render) 的指针。

当处理程序被触发时,上下文将取决于源如何执行处理程序。在大多数情况下,提供给处理程序的上下文将是源的上下文(甚至可能是 null)。例如,window.addEventListener( 'resize', handler ); 将通过调用等效的 handler.call( window, event );

来触发调整大小事件

有几种方法可以解决这个问题。

箭头函数隐式 this

箭头函数使用定义它们的上下文的 this。因此,如果您的箭头函数是在您的类的成员函数中定义的,并且该成员函数正在该类的实例的上下文中执行,那么该箭头函数体内的 this 将引用该实例。

class Test{

  constructor(){

    this.name = "Test"

    // Arrow function defined within the context of an instance:
    window.addEventListener( 'resize', () => {
      console.log( this.name ) // prints: "Test"
    } )

    // Normal function will execute with "window" as its "this"
    window.addEventListener( 'resize', function() {
      console.log( this.name ) // results may vary based on your browser, but "this" is not an instance of Test
    } )

  }

}

将函数绑定到上下文

使用 bind 可以将函数附加到指定的上下文。 bind 函数返回一个永久绑定到提供的上下文的新函数。

class Test{

  constructor(){

    this.name = "Test"

    this.boundRender = this.render.bind( this ) // returns "render" bound permanently to "this"

    window.addEventListener( 'resize', this.boundRender )

  }

  render(){
    console.log( this.name )
  }

}

上面的例子仍然会在控制台打印“Test”。尽管 resize 事件想要使用 window 上下文执行处理程序,但 bind 已覆盖该处理程序并将处理程序锁定为在其绑定到的实例的上下文中执行。

答案 1 :(得分:0)

感谢 TheJim01!下面是使用 bind 方法的最终解决方案(带箭头的解决方案不起作用)。也可以在 https://codepen.io/jimver04/pen/XWjLPLo

中解析找到
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r122/examples/jsm/controls/OrbitControls.js';



class Main {

  constructor(canvasId)
  {
    this.canvas = document.querySelector(canvasId);

    this.renderer = new THREE.WebGLRenderer({canvas: this.canvas});

    this.fov = 75;
    this.aspect = 2;  // the canvas default
    this.near = 0.1;
    this.far = 5;
    this.camera = new THREE.PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
    this.camera.position.z = 2;

    this.controls = new OrbitControls(this.camera, this.canvas);
    this.controls.target.set(0, 0, 0);
    this.controls.update();

    this.scene = new THREE.Scene();


    this.color = 0xFFFFFF;
    this.intensity = 1;
    this.light = new THREE.DirectionalLight(this.color, this.intensity);
    this.light.position.set(-1, 2, 4);
    this.scene.add(this.light);


    this.boxWidth = 1;
    this.boxHeight = 1;
    this.boxDepth = 1;
    this.geometry = new THREE.BoxGeometry(this.boxWidth, this.boxHeight, this.boxDepth);
    
     this.boundRender = this.render.bind( this );
  }

  makeInstance(geometry, color, x){

    let material = new THREE.MeshPhongMaterial(color);

    let cube = new THREE.Mesh(geometry, material);
    this.scene.add(cube);
    cube.position.x = x;

    return cube;
  }



  resizeRendererToDisplaySize(renderer) {
    this.canvas = this.renderer.domElement;
    let width = this.canvas.clientWidth;
    let height = this.canvas.clientHeight;
    let needResize = this.canvas.width !== width || this.canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }

    return needResize;
  }

  render() {
    
    if (this.resizeRendererToDisplaySize(this.renderer)) {
      this.canvas = this.renderer.domElement;
      this.camera.aspect = this.canvas.clientWidth / this.canvas.clientHeight;
      this.camera.updateProjectionMatrix();
    }
    
    this.renderer.render(this.scene, this.camera);
    
  }

  starter() {

    this.makeInstance(this.geometry, 0x44aa88,  0);
    this.makeInstance(this.geometry, 0x8844aa, -2);
    this.makeInstance(this.geometry, 0xaa8844,  2);

    this.render();

    this.controls.addEventListener('change', this.boundRender );

    window.addEventListener('resize', this.boundRender);

    // note: this is a workaround for an OrbitControls issue
    // in an iframe. Will remove once the issue is fixed in
    // three.js

    window.addEventListener('mousedown', (e) => {
      e.preventDefault();
      window.focus();
    });

    window.addEventListener('keydown', (e) => {
      e.preventDefault();
    });
  }
}



let main = new Main('#a');
main.starter();

let main2 = new Main('#b');
main2.starter();