0

Edit: This question and answer is much better and give a sense than the other answer that says 'duplicate'. Worth keeping this for futher refference, plus this is beginnner friendly as I have no clue what callbacl is and all of the confusing example of foo boo bar...

I get a value of null when calling a class inside a method of another class. I want to animate the object in JavaScript canvas.

I'm making a small animation with JavaScrpt canvas and I'm also learning about classes and JavaScript in general. So a newbie question.

How can I remove the error and make this work?

Here is the codepen: https://codepen.io/Aurelian/pen/mGWVbq?editors=0010

Here is the JS:

'use strict';
function random(min, max) {
  return Math.random() * (max - min) + min;
}
   /*
    * ------------------------------------------
    * *-----------------------------
    *  User Event
    * *-----------------------------
    * ------------------------------------------
    */
   class UserEvent {
      constructor(canvasBody) {
         this.UP_ARROW = 38 || 87,
         this.RIGHT_ARROW =39 || 68,
         this.DOWN_ARROW = 37 || 83,
         this.LEFT_ARROW = 40 || 65,
         this.keys = []

         canvasBody.addEventListener('keydown', (e) => {
            this.keys[e.keyCode] = true;
         });

         canvasBody.addEventListener('keyup', (e) => {
            this.keys[e.keyCode] = false;
         });
      }

      checkKey(key) {
         return this.keys[key];
      }

//       ufoMove() {
//          this.canvasBody.checkKey(this.UP_ARROW)
//          this.canvasBody.checkKey(this.RIGHT_ARROW)
//          this.canvasBodycheckKey(this.DOWN_ARROW)
//          this.canvasBody.checkKey(this.LEFT_ARROW)
//          console.log(this.canvasBody.leftArrow = this.checkKey(this.LEFT_ARROW))

//          if(this.UP_ARROW) {
//             this.x += this.x * this.velocity.x
//          }
//       }
   }


   /*
    * ------------------------------------------
    * *-----------------------------
    *  UFO
    * *-----------------------------
    * ------------------------------------------
    */
   class Ufo {
      constructor(x, y) {
         this.x = x,
         this.y = y,
         this.velocity = {
            x: 3,
            y: 3
         }
      }

      draw(c) {
         c.save()
         c.beginPath()
         c.arc(this.x, this.y, 50, 0, Math.PI * 2, false)
         c.fillStyle = "#fff";
         c.shadowColor = "#e3eaef";
         c.shadowBlur = 20;
         c.fill()
         c.closePath()
         c.restore()
      }

      update(c) {
         this.draw(c)
         // Get the keys first
         // this.EventUser.ufoMove(c);
         this.x = this.x + random(-5,5);
         this.y = this.y + random(-5,5);
         //    this.x = this.x + this.velocity.x;
         // }
      }
   }

   /*
    * ------------------------------------------
    * *-----------------------------
    *  Canvas
    * *-----------------------------
    * ------------------------------------------
    */      
   class CanvasDisplay {
      constructor() {
         this.canvas = document.querySelector('canvas');
           this.ctx = this.canvas.getContext('2d');
         this.stageConfig = {
              width: window.innerWidth,
              height: window.innerHeight
         };         
         this.canvas.width = this.stageConfig.width;
         this.canvas.height = this.stageConfig.height;

         this.backgroundGradient = this.ctx.createLinearGradient(0, 0, 0, this.canvas.height);
         this.backgroundGradient.addColorStop(0, '#171e26');
         this.backgroundGradient.addColorStop(1, '#3f586b');

         this.Ufo = new Ufo(this.canvas.width / 2, this.canvas.height / 2);
         this.UserEvent = new UserEvent(document.body);
      }

      animate() {
         this.ctx.fillStyle = this.backgroundGradient;
         this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

         this.Ufo.update(this.ctx)
         window.requestAnimationFrame(this.animate);
      }
   }

   let canvasDisplay = new CanvasDisplay();
   canvasDisplay.animate();
  • 1
    what is `this.UP_ARROW = 38 || 87,` doing? it takes the first truthy value for assignment. for a later check, you need a data structure like an array. or an object, depending on the use of it. – Nina Scholz Sep 02 '18 at 20:24
  • @NinaScholz I assume it's an attempt (not working sure) to use UP or W, however the `ufoMove` function that uses it is commented out so doesn't appear related to the question – Nick is tired Sep 02 '18 at 20:26
  • Oh, I was trying to add keyboard event and on each keypress, make the object move, but I though to start simpler, and just use Math.random to move the start, and then improve on it. Does that cause the issue? I'm trying to make the object move and draw the frames but not sure if I understand correctly what exactly am doing there. Idealy later I will want to add keyboard events to it. But it is commented out yeah, I suppose it breaks it ? But if its commented out shoudnt break it right. – Aurelian Spodarec Sep 02 '18 at 20:26

2 Answers2

3

The problem is

window.requestAnimationFrame(this.animate);

When you pass a function as a callback, it does not retain the calling context it would have if it was called normally, eg, as this.animate(), which would have a calling context of this. It's like

function runCallback(callback) {
  callback();
}

which will run callback without a calling context, by default.

So, in animate, when the first line is

this.ctx.fillStyle = ...

an error occurs, because this does not refer to the instantiated object, when animate has been called due to requestAnimationFrame.

Instead, either explicitly bind the calling context to this when you pass this.animate to requestAnimationFrame:

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

or pass a function that calls this.animate():

window.requestAnimationFrame(() => this.animate());
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • That worked. I'm bit new, how does bind and callback relate to this? Or when should I use bind? I have tried to read about bind before but its a bit confusing. Do you have any good resource to read futher on this? But my logic is somewhat correct, right? I'm not doing too bad for a newbie I suppose with what I'm creating? Its the correct way of doing it? I'm trying to learn concepts by building something. Was reading eloquent JS and some tutorials on ES6. – Aurelian Spodarec Sep 02 '18 at 20:31
  • You can read MDN's docs on `this` [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this), and there are many posts about it. I didn't look much at the other parts of your code, I was just trying to solve the error you asked about. – CertainPerformance Sep 02 '18 at 20:34
  • Yes. I understand to a degree what 'this' means. Okay. Starts to make sense I think. Because of the context. A normal function context 'this' will be the function. An arrow function 'this' will be outside. So we need to .. Ok ok. I think I need to read a bit more and study on this matter again to grasp the concept fully, because I get confused. Thanks for the answer and the link! – Aurelian Spodarec Sep 02 '18 at 20:36
3

You have to bind scope at:

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

since anonymous functions dont bind it by it self.

Edit: Didnt seen @CertainPerformance answer.

atl3
  • 107
  • 5