Angular-CLI & ThreeJS

2020-05-19 02:45发布

问题:

I have been trying to add the proper npm dependencies to add THREE to my Angular-CLI project. With CLI changing so rapidly in the past few months, I haven't been able to find working source.

Here are a few ideas...

  • Piggyback with scripts

    This was my first attempt, to simply add <script src="three.js"></script> to the index.html file. However I'm having trouble working with the javascript in the typescript interface.

  • Webpack

    This was my second attempt, but I ran into several documentation problems. Angular-cli doesn't seem to have a consistent way of using webpacks. There are four different ways of implementing webpacks. I wasn't able to get any working with THREE.

  • Bundles

    This seems like a hack, and a poor/lengthy one. It would be to add bundles to the THREE library so that it can be interpreted angular 2.

I'm still currently working on making a Angluar-CLI + THREE.js project. If I don't see progress in the next week, I may drop angular-cli. Any advice/sources would be greatly appreciated.

回答1:

As of angular-cli 1.0.0:

  1. npm install three --save
  2. npm install @types/three
  3. Add a div element in AppComponent.html: <div #rendererContainer></div>
  4. Import three.js in AppComponent.ts: import * as THREE from 'three';
  5. Get a handle to your div element with @ViewChild('rendererContainer') rendererContainer: ElementRef;
  6. Do the necessary setup in your constructor / lifecycle methods. Note: the ViewChild is not available until ngAfterViewInit.

Full AppComponent:

import {Component, ViewChild, ElementRef} from '@angular/core';
import * as THREE from 'three';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    @ViewChild('rendererContainer') rendererContainer: ElementRef;

    renderer = new THREE.WebGLRenderer();
    scene = null;
    camera = null;
    mesh = null;

    constructor() {
        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
        this.camera.position.z = 1000;

        const geometry = new THREE.BoxGeometry(200, 200, 200);
        const material = new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true});
        this.mesh = new THREE.Mesh(geometry, material);

        this.scene.add(this.mesh);
    }

    ngAfterViewInit() {
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.rendererContainer.nativeElement.appendChild(this.renderer.domElement);
        this.animate();
    }

    animate() {
        window.requestAnimationFrame(() => this.animate());
        this.mesh.rotation.x += 0.01;
        this.mesh.rotation.y += 0.02;
        this.renderer.render(this.scene, this.camera);
    }
}

Full AppComponent.html:

<div #rendererContainer></div>

If you want to use some of the additional scripts:

You may end up wanting to use some of the additional scripts, such as loaders and controls. Most of these haven't been written as modules, but instead add functionality to the THREE namespace on the window when loaded. Because of this I ended up telling the angular-cli to just load up my scripts manually by adding the following to my .angular-cli.json file:

{
  "apps": [
    {
      "scripts": [
        "../node_modules/tween.js/src/Tween.js",
        "../node_modules/three/build/three.js",
        "../node_modules/stats.js/build/stats.min.js",
        "../vendor/VRMLLoader.js",
        "../vendor/OrbitControls.js"
      ],
      ...

Note that you will also need to deal with the fact that your three.js @types file does not define any types for these additional scripts. Ideally I would like to extend the existing type definitions, but for the time being I am just foregoing type hinting for three.js to get around the errors by adding declare const THREE: any to the top of my files that use three.js. If you find a good way to instead extend the existing definitions from @types/three, please report back!

To handle resizing the window:

While I am at it I will also mention that resizing your window can cause things like raycasting (which I use to decide if an object is clicked) to not work correctly anymore. To fix this just do:

@HostListener('window:resize', ['$event'])
onWindowResize(event) {
    this.renderer.setSize(event.target.innerWidth, event.target.innerHeight)
}