samuelk71
samuelk71

Reputation: 311

Creating and using classes with canvas

I am having some trouble with this because Javascript just seems terrible for classes and the implementation is interesting. I am trying to get this block working so I can create multiple triangles:

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var phase = 0;
var tau = 2 * Math.PI;

function animate() {
    requestAnimationFrame(animate);
    var sides = 3;
    var size = 100;
    var centerX = canvas.width / 2;
    var centerY = canvas.height / 2;
    phase += 0.005 * tau;

    context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
    for (var i = 0; i <= sides; i++) {
        context[i ? 'lineTo' : 'moveTo'](
            centerX + size * Math.cos(phase + i / sides * tau),
            centerY + size * Math.sin(phase + i / sides * tau)
        );
    }
    context.stroke();
}

animate(); 

And here I tried making it into a class:

var canvas = document.querySelector('canvas');
        var context = canvas.getContext('2d');
        var phase = 0;
        var tau = 2 * Math.PI;

        function Triangle(cntx, canvs) {
            this.ctx = cntx;
            this.canv = canvs;
            this.draw = drawTriangle;
        }
        function drawTriangle() {
            requestAnimationFrame(drawTriangle);
            var sides = 3;
            var size = 100;
            var centerX = this.canv.width / 2;
            var centerY = this.canv.height / 2;
            phase += 0.005 * tau;

            this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
            this.ctx.beginPath();
            for (var i = 0; i <= sides; i++) {
                this.ctx[i ? 'lineTo' : 'moveTo'](
                    centerX + size * Math.cos(phase + i / sides * tau),
                    centerY + size * Math.sin(phase + i / sides * tau)
                );
            }
            this.ctx.stroke();
        }

        var triangle1 = new Triangle(context,canvas);
        triangle1.draw();

The problem is that it just draws the triangle once so I am not really sure what I am doing wrong here.

Upvotes: 2

Views: 107

Answers (2)

Blindman67
Blindman67

Reputation: 54026

You have two ways you can do it as a javascript object (don't think of them as classes).

First way using prototype to define object methods.

function Triangle(cntx, canvs) {  // define the triange
    this.ctx = cntx;
    this.canv = canvs;
    this.draw = this.drawTriangle.bind(this);
}
// this creates and compiles the draw function ready to be used for any Triangle object you create.
Triangle.prototype.drawTriangle = function() {  // define the draw method as part of
                                        // triangle's prototype
    requestAnimationFrame(this.draw);   // all properties of Triangle.prototype can be referenced via 'this'
    var sides = 3;
    var size = 100;
    var centerX = this.canv.width / 2;
    var centerY = this.canv.height / 2;
    phase += 0.005 * tau;

    this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
    this.ctx.beginPath();
    for (var i = 0; i <= sides; i++) {
        this.ctx[i ? 'lineTo' : 'moveTo'](
            centerX + size * Math.cos(phase + i / sides * tau),
            centerY + size * Math.sin(phase + i / sides * tau));
    }
    this.ctx.stroke();
}

var triangle1 = new Triangle(context, canvas);
triangle1.draw();

Or you can create a safer Triangle with the following. In this case we minimise the use of the this token by using closure to encapsulate the variables we want ctx and canv are no longer exposed and only accessible from within the triangle object calls. Using closure is a little faster when running, abut slower at creation.

function Triangle(cntx, canvs) {
    var ctx = cntx;   // create closure vars
    var canv = canvs;
    this.draw = (function() { // draw run faster because it does not have
                             // to search the prototype for the values
                             // of ctx and canv.
        requestAnimationFrame(this.draw);
        var sides = 3;
        var size = 100;
        var centerX = this.canv.width / 2;
        var centerY = this.canv.height / 2;
        phase += 0.005 * tau;

        ctx.clearRect(0, 0, canv.width, canv.height); // dont need the this keyword
        ctx.beginPath();
        for (var i = 0; i <= sides; i++) {
            ctx[i ? 'lineTo' : 'moveTo'](
                centerX + size * Math.cos(phase + i / sides * tau),
                centerY + size * Math.sin(phase + i / sides * tau));
        }
        ctx.stroke();
    }).bind(this); // because requestAnimationFrame sets this wee need to bind the current this to the method.
}
var triangle1 = new Triangle(context, canvas);  // It takes a little longer to invoke
                                                // because it will need to create and compile
                                                // the draw function.

triangle1.draw();  // but call this now runs a little faster.

The differences in speed in this case is very minor, the advantage of one over the other will depend on how often you create the new object and how often you call its methods. Use prototype if you create often, use closure if you create once and use often.

Upvotes: 0

Buzinas
Buzinas

Reputation: 11725

The problem here is that you're calling the requestAnimationFrame and passing a callback to the same function, but the this keyword will refer to the window object, and not your class anymore.

So, you must explicit that you want to set the context of the callback function as the same context you are, and you can achieve that by calling .bind(this). Take a look at the example below:

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var phase = 0;
var tau = 2 * Math.PI;

function Triangle(cntx, canvs) {
  this.ctx = cntx;
  this.canv = canvs;
  this.draw = drawTriangle;
}

function drawTriangle() {
  requestAnimationFrame(drawTriangle.bind(this));
  var sides = 3;
  var size = 100;
  var centerX = this.canv.width / 2;
  var centerY = this.canv.height / 2;
  phase += 0.005 * tau;

  this.ctx.clearRect(0, 0, this.canv.width, this.canv.height);
  this.ctx.beginPath();
  for (var i = 0; i <= sides; i++) {
    this.ctx[i ? 'lineTo' : 'moveTo'](
      centerX + size * Math.cos(phase + i / sides * tau),
      centerY + size * Math.sin(phase + i / sides * tau)
    );
  }
  this.ctx.stroke();
}

var triangle1 = new Triangle(context, canvas);
triangle1.draw();
<canvas></canvas>

Upvotes: 1

Related Questions