Import and use three.js library in vue component

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


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.


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>