Import and use three.js library in vue component
Asked Answered
M

6

12

Could someone please explain me how I correctly import and use the three.js library in a vue component?

After many many searches It became clear to me that most people use the following line to import three.js in a vue component, however I think it's outdated (usef for older three.js document or used in older vue versions).

import * as THREE from './js/three.js';

Unfortunately this doesn't seem to work for me as I get the following warnings when compiling my vue project afterwards. (Note that the project actually doesn't compile correctly and I get an empty file when I browse to it). enter image description here

I tried many other common ways to import the three.js that didn't work either!

I'm no Vue expert at all, but three.js contains the following code block with exports, I think this may affect the way I need to import this library to avoid the compiling warnings.

exports.WebGLRenderTargetCube = WebGLRenderTargetCube;
exports.WebGLRenderTarget = WebGLRenderTarget;
exports.WebGLRenderer = WebGLRenderer;
exports.ShaderLib = ShaderLib;
exports.UniformsLib = UniformsLib;
exports.UniformsUtils = UniformsUtils;
exports.ShaderChunk = ShaderChunk;
exports.FogExp2 = FogExp2;
exports.Fog = Fog;
exports.Scene = Scene;
(and so one...)


The complete Vue component file that I'm using for my project.

Memo answered 16/12, 2017 at 20:20 Comment(0)
C
5

You can use a require statement like this:

const THREE = require('THREE')

But some plugins assume THREE is available on window, so you may want to do window.THREE = require('THREE')

I don't have much experience with import statements, but the above should work.

Coulee answered 16/12, 2017 at 20:29 Comment(0)
S
58

For anyone who just want to try out a basic setup. This is the three.js example in a vue component 'ThreeTest'. Project setup with vue-cli 'vue init webpack ProjectName', 'cd ProjectName', 'npm install three --save' and replace the 'HelloWorld' component with this one:

<template>
    <div id="container"></div>
</template>

<script>
import * as Three from 'three'

export default {
  name: 'ThreeTest',
  data() {
    return {
      camera: null,
      scene: null,
      renderer: null,
      mesh: null
    }
  },
  methods: {
    init: function() {
        let container = document.getElementById('container');

        this.camera = new Three.PerspectiveCamera(70, container.clientWidth/container.clientHeight, 0.01, 10);
        this.camera.position.z = 1;

        this.scene = new Three.Scene();

        let geometry = new Three.BoxGeometry(0.2, 0.2, 0.2);
        let material = new Three.MeshNormalMaterial();

        this.mesh = new Three.Mesh(geometry, material);
        this.scene.add(this.mesh);

        this.renderer = new Three.WebGLRenderer({antialias: true});
        this.renderer.setSize(container.clientWidth, container.clientHeight);
        container.appendChild(this.renderer.domElement);

    },
    animate: function() {
        requestAnimationFrame(this.animate);
        this.mesh.rotation.x += 0.01;
        this.mesh.rotation.y += 0.02;
        this.renderer.render(this.scene, this.camera);
    }
  },
  mounted() {
      this.init();
      this.animate();
  }
}
</script>

<style scoped>
    //TODO give your container a size.
</style>
Seanseana answered 15/3, 2018 at 9:11 Comment(10)
Thanks! Was looking for something just like this, will try if it works :)Whirlwind
Worked fine! Needed some adjustments to make it work for a component that is not the only child of the body, but suggested this as an edit to this code.Whirlwind
IMO this answer should be the accepted one. It's a lot more detailed and clear.Wafture
But when I tried to split the init and animate functions from .vue file into another .js file, to keep my vue file's script section more concentrate, I got nothing rendered on screen. I can tell from my console logs that my init() and animate() function gets called by three.js core, and my animate function got correct initialization result from previous init call. I'm pretty green to Vue programming so any idea to point out what's wrong with my case would be appreciated.Weigh
@Weigh Since I do not know what you have tried I give you a quick and dirty solution for the animation. The code should only be used for testing ;) It is important that you need the context that was created in the init function in the animation: animation.js let context; function set(c) { context = c; }; let animate = function animate() { requestAnimationFrame(animate); context.mesh.rotation.x += 0.01; context.mesh.rotation.y += 0.02; context.renderer.render(context.scene, context.camera); }; export { animate, set }; -> next commentSeanseana
Extension to the vue file: import * as animations from './animate.js' mounted() { this.init(); animations.set({camera: this.camera, scene: this.scene, renderer: this.renderer, mesh: this.mesh}); animations.animate(); }Seanseana
I had to add this lines of css code to my App.vue, because my canvas height was 0: canvas { width: 100%; height: 100% } Jeanmariejeanna
I had to add: <style scoped> #container { height: 200px; } </style>Ibis
How do you ensure that the above script isn't called every time the threejs part goes out of scope? - i believe vue destroys children when they go out of view. It would be expensive to recreate the 3D view each time for example the user clicks between tabsRoath
If you're on Vue3 and get Uncaught TypeError: 'get' on proxy: property 'modelViewMatrix' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<Matrix4>' but got '#<Matrix4>'), just remove all data variables, the line with return() { camera, scene, renderer, mesh }. Apparently it's a Vue3 bug, has to do with the reactivity of the variables https://mcmap.net/q/890564/-threejs-component-working-in-vuejs-2-but-not-3Zaxis
H
6

Since the given answers were not working for me, I'll share the initial setup that worked for me. I am using Nuxt.js in my example.

If this works, a green rotating cube should appear.

enter image description here

<template>
  <div id="container"></div>
</template>
<script>
import * as THREE from 'three'

export default {
  name: 'ThreeTest',
  data() {
    return {
      cube: null,
      renderer: null,
      scene: null,
      camera: null
    }
  },
  methods: {
    init: function() {
      this.scene = new THREE.Scene()
      this.camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      )

      this.renderer = new THREE.WebGLRenderer()
      this.renderer.setSize(window.innerWidth, window.innerHeight)
      document.body.appendChild(this.renderer.domElement)

      const geometry = new THREE.BoxGeometry(1, 1, 1)
      const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
      this.cube = new THREE.Mesh(geometry, material)
      this.scene.add(this.cube)

      this.camera.position.z = 5

      const animate = function() {}
    },
    animate: function() {
      requestAnimationFrame(this.animate)

      this.cube.rotation.x += 0.01
      this.cube.rotation.y += 0.01

      this.renderer.render(this.scene, this.camera)
    }
  },
  mounted() {
    this.init()
    this.animate()
  }
}
</script>
Hadik answered 10/6, 2019 at 22:36 Comment(0)
C
5

You can use a require statement like this:

const THREE = require('THREE')

But some plugins assume THREE is available on window, so you may want to do window.THREE = require('THREE')

I don't have much experience with import statements, but the above should work.

Coulee answered 16/12, 2017 at 20:29 Comment(0)
W
4

This is a follow up of the previous answer given by @PolygonParrot. As the limitation of comment length I had to put my answer here.

Thanks very much for your answer, @PolygonParrot. It helped me a lot! Based on your demo code I figured out that the key to separate animation code from Vue component code is to pass a correct animation context to the module defined in animation.js. I think my first try failed maybe because of the "closure" feature of functional programming, which is a painful concept to adapt to for an ancient programmer like me. My animation.js now looks like this:

import * as THREE from 'three'
// passed in container id within which this animation will be shown
export function createBoxRotationContext(container) {
var ctx = new Object();
ctx.init = function init() {
    ctx.container = container;
    ctx.camera = new THREE.PerspectiveCamera(70, ctx.container.clientWidth/ctx.container.clientHeight, 0.01, 10);
    ctx.camera.position.z = 1;

    ctx.scene = new THREE.Scene();

    let geometry = new THREE.BoxGeometry(0.3, 0.4, 0.5);
    let material = new THREE.MeshNormalMaterial();
    ctx.box = new THREE.Mesh(geometry, material);

    ctx.fnhelper   = new THREE.FaceNormalsHelper(ctx.box, 0.3, 0x0000ff, 0.1);
    ctx.axes = new THREE.AxesHelper(5);

    ctx.scene.add(ctx.box);
    ctx.scene.add(ctx.axes);
    ctx.scene.add(ctx.fnhelper);

    ctx.renderer = new THREE.WebGLRenderer({antialias: true});
    ctx.renderer.setSize(ctx.container.clientWidth, ctx.container.clientHeight);
    ctx.container.appendChild(ctx.renderer.domElement);
},
ctx.animate = function animate() {
    requestAnimationFrame(animate);
    ctx.box.rotation.x += 0.01;
    ctx.box.rotation.y += 0.02;
    ctx.fnhelper.update();
    ctx.renderer.render(ctx.scene, ctx.camera);
}
return ctx;
};

And the .vue file now looks like:

<script>
import * as animator from '@/components/sandbox/animation.js'

export default {
    name: 'Sandbox',
    data() {
      return {
        camera: null,
        scene: null,
        renderer: null,
        mesh: null
      }
    },
    mounted() {
        let context = animator.createBoxRotationContext(
            document.getElementById('three-sandbox')
        );
        context.init();
        context.animate();
    }
}
</script\>

As my scene grows bigger by adding more stuff in, my vue template can keep clean and hide animation logic behind the view. I think my use of context here still looks weird but at least it serve my purpose well just for now. render result

Weigh answered 7/12, 2018 at 4:25 Comment(0)
S
0
import * as THREE from "three";

Worked well for me!

Saleable answered 9/10, 2020 at 15:41 Comment(0)
G
-1

Thank you Polygon Parrot for your template, It's what i came here looking for. Id like to include the window resize method specific for your example, just to make your template a little more complete.

methods:{ ...
onWindowResize(){
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix()
    }
created(){
        window.addEventListener('resize', () => {this.onWindowResize()})
  }
Greeting answered 25/1, 2020 at 22:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.