Search code examples
javascriptthree.jsobject-literal

Three.js "Uncaught TypeError: Cannot read property 'render' of undefined" error with object literal


I'm trying to convert my codes to object literal style. I can create the scene but I got problems with animation.

In this line I'm getting the "Uncaught TypeError: Cannot read property 'render' of undefined" error.

this.renderer.render(this.scene, this.camera);

This is my object:

var three = {
    objects: function() {
        /*objects*/
    },
    createScene: function() {
        this.container = document.getElementById("container");

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.001, (26 * 10));
        this.camera.position.set(26, (26 / 2), 26);

        window.addEventListener("resize", function() {
            this.camera.aspect = window.innerWidth / window.innerHeight;
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(window.innerWidth, window.innerHeight);
        });

        this.objects();

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
    },
    animate: function() {
        this.renderer.render(this.scene, this.camera);
        requestAnimationFrame(this.animate);
    },
    render: function() {
        this.createScene();
        this.animate();
    }
};

three.render();

Solution

  • Check my modified example (I borrowed your objects function to render a cube.. just for fun of it:) )

    Basically, you need to pass the context along with your animate method using the Function.prototype.bind

    requestAnimationFrame(this.animate.bind(this));
    

    ..what happens behind the curtain is that the first call of this.renderer.render(this.scene, this.camera); happens just fine without issues because the context is passed along with this.animate(); method. However, the second time the animate is called, by requestAnimationFrame method, the context is not there. Hence you need to pass it manually.

    var three = {
        objects: function() {
            /*objects*/
            var geometry = new THREE.BoxBufferGeometry( 3, 3, 3 );
    				var material = new THREE.MeshBasicMaterial( { color: 0xffaa00 } );
    				this.mesh = new THREE.Mesh( geometry, material );
            this.mesh.position.set(24, 14, 12);
    				this.scene.add( this.mesh );
        },
        createScene: function() {
            this.container = document.getElementById("container");
    
            this.scene = new THREE.Scene();
    
            this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.001, (26 * 10));
            this.camera.position.set(26, (26 / 2), 26);
    
            window.addEventListener("resize", function() {
                this.camera.aspect = window.innerWidth / window.innerHeight;
                this.camera.updateProjectionMatrix();
                this.renderer.setSize(window.innerWidth, window.innerHeight);
            }.bind(this));
    
            this.objects();
    
            this.renderer = new THREE.WebGLRenderer();
            this.renderer.setPixelRatio(window.devicePixelRatio);
            this.renderer.setSize(window.innerWidth, window.innerHeight);
            this.container.appendChild(this.renderer.domElement);
        },
        animate: function() {
            this.mesh.rotation.x += 0.005;
    				this.mesh.rotation.y += 0.01;
    
            this.renderer.render(this.scene, this.camera);
            requestAnimationFrame(this.animate.bind(this));
        },
        render: function() {
            this.createScene();
            this.animate();
        }
    };
    
    three.render();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.min.js"></script>
    
    <div id="container"></div>