WOLF CRECENT
WOLF CRECENT

Reputation: 211

How do you use canvas as a class?

I am trying to create a class which has a canvas as an object in an attempt learn more about classes. I wrote the following code but it gives nothing but a blank screen.


    class Canvas
{
    constructor(canvasId = 'canvas', dimension = '2d', canvasWidth = document.body.clientWidth,canvasHeight = window.innerHeight,canvasDisplay = 'block',canvasOverflow = 'hidden')
    {
        // Initialize Canvas and Context variables
        this.can = document.getElementById(canvasId);
        this.ctx = this.can.getContext(dimension);
        //  Set canvas properties
        this.can.width = canvasWidth;
        this.can.height = canvasHeight;
        this.can.style.display = canvasDisplay;
        this.can.style.overflow = canvasOverflow;
        this.count = 0;
    }

    display(ctx) 
    {
        var ctx = ctx;
        var scrollSS = new Image();
        scrollSS.src = "../../../Assets/Media/Images/Scroll/ScrollSpriteSheet.png";
        ctx.drawImage(scrollSS,0,0,102,345,10,0,canvas.width / 10,canvas.height);
    }

    animate(ctx) 
    {   
        var ctx = ctx;
        console.log(this.count);
        this.count++;
        this.display(ctx)
        requestAnimationFrame(this.animate(ctx));   
    }


}

var canvas = new Canvas();
console.log(canvas.ctx);
canvas.animate(canvas.ctx);

Am I doing something wrong?

Upvotes: 1

Views: 182

Answers (1)

Nick Parsons
Nick Parsons

Reputation: 50684

Your issue is that you are calling animate() again and again within your requestAnimationFrame() method. You instead need to let JS call the function animate for you when requestAnimationFrame() decides to do so.

This means you need to pass the animate function into requestAnimationFrame, not the actual function call (because when you call the function, you're really passing through its return value):

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

Since JS will handle the calling of the animate method, the this inside of animate() (when invoked) will not be your Canvas object, but rather the window. To make this refer to your Canvas object you'll need to .bind(this) to your animate function. The above is equivalent to using an arrow function:

requestAnimationFrame(() => this.animate());

Your other issue is that you're trying to display the image before it has loaded. You firstly need to load the image and then display it. If your image is going to stay the same, I recommend you load it before you draw, rather than loading it every time you want to redraw (see second code snippet).

Also, ctx is part of your canvas instance. Each canvas object will have a .ctx property as it is defined in the constructor. So, there is no need to pass it through as an argument to animate or display, as you can access it by using this.ctx.

See example below (note the image is a temporary image):

class Canvas {
  constructor(canvasId = 'canvas', dimension = '2d', canvasWidth = document.body.clientWidth, canvasHeight = window.innerHeight, canvasDisplay = 'block', canvasOverflow = 'hidden') {
    // Initialize Canvas and Context variables
    this.can = document.getElementById(canvasId);
    this.ctx = this.can.getContext(dimension);
    //  Set canvas properties
    this.can.width = canvasWidth;
    this.can.height = canvasHeight;
    this.can.style.display = canvasDisplay;
    this.can.style.overflow = canvasOverflow;
    this.count = 0;
  }

  display() {
    var ctx = this.ctx;
    var scrollSS = new Image();
    scrollSS.src = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRha_-J-uQR_8pUrUQOiPYZ_JXRoqfoqDt8BO8icSfkworyz9woQQ&s";
    scrollSS.addEventListener('load', e => {
      ctx.drawImage(scrollSS, 0, 0, 102, 345, 10, 0, this.can.width / 10, this.can.height);
    });
  }

  animate() {
    this.count++;
    this.display();
    requestAnimationFrame(this.animate.bind(this));
  }
}

var canvas = new Canvas();
canvas.animate();
<canvas id="canvas" style="border: 1px solid black"></canvas>

Here is an approach using Promises that you might want to take if you only need to load the one image:

class Canvas {
  constructor(canvasId = 'canvas', dimension = '2d', canvasWidth = document.body.clientWidth, canvasHeight = window.innerHeight, canvasDisplay = 'block', canvasOverflow = 'hidden') {
    // Initialize Canvas and Context variables
    this.can = document.getElementById(canvasId);
    this.ctx = this.can.getContext(dimension);
    //  Set canvas properties
    this.can.width = canvasWidth;
    this.can.height = canvasHeight;
    this.can.style.display = canvasDisplay;
    this.can.style.overflow = canvasOverflow;
    this.count = 0;
  }

  loadImage(url) {
    var img = new Image();
    img.src = url;
    return new Promise((resolve) => {
      img.addEventListener('load', function(e) {
        resolve(this);
      });
    });
  }

  display(img) {
    var ctx = this.ctx;
    ctx.drawImage(img, 0, 0, 102, 345, 10, 0, this.can.width / 10, this.can.height);
  }

  animate(img) {
    this.count++;
    this.display(img);
    requestAnimationFrame(this.animate.bind(this, img));
  }
}

var canvas = new Canvas();
var img_url = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRha_-J-uQR_8pUrUQOiPYZ_JXRoqfoqDt8BO8icSfkworyz9woQQ&s"; 
canvas
  .loadImage(img_url)
  .then(function(image) {
    canvas.animate(image);  
  });
<canvas id="canvas" style="border: 1px solid black"></canvas>

Upvotes: 2

Related Questions