59

I'm using webkitRequestAnimationFrame but I'm having trouble using it inside of an object. If I pass the this keyword it will use window and I can't find a way for it to use the specified object instead.

Example:

Display.prototype.draw = function(){
  this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
  //Animation stuff here.

  window.webkitRequestAnimationFrame(this.draw);
};

I have also tried this but to no avail:

Display.prototype.draw = function(){
  this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
  //Animation stuff here.

  var draw = this.draw;
  window.webkitRequestAnimationFrame(draw);
};
Ryan
  • 3,726
  • 3
  • 26
  • 38

7 Answers7

102

I'm trying to pass display.draw which is the function in which webkitRequestAnimationFram resides.

webkitRequestAnimationFrame will presumably call the function you pass in, something like this:

function webkitRequestAnimationFrame(callback)
{
    // stuff...
    callback();
    // other stuff...
}

At this point, you have dissociated (detached) the draw function from its invocation context. You need to bind the function (draw) to its context (the instance of Display).

You can use Function.bind, but this requires JavaScript 1.8 support (or just use the recommended patch).

Display.prototype.draw = function()
{
    // snip...

    window.webkitRequestAnimationFrame(this.draw.bind(this));
};
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 1
    I'm trying to pass `display.draw` which is the function in which `webkitRequestAnimationFram` resides. – Ryan May 19 '11 at 21:52
  • 1
    Oh, I think I see the problem: you can pass the function, but `webkitRequestAnimationFrame` will invoke it later, and `this` will not point to the right object because you've "detached" the function from its object. See my edit (pending). – Matt Ball May 19 '11 at 21:54
  • The `bind` method works perfectly (Never knew about that, thnx :] ) but passing `this.draw();` with a closure still throws an error. `Uncaught TypeError: Object [object DOMWindow] has no method 'draw'` – Ryan May 19 '11 at 22:09
  • Hmm, still doesn't work `Uncaught TypeError: Object # has no method 'fn'` but don't worry about it, the bind method is good enough. Thanks for your help. – Ryan May 19 '11 at 22:37
  • I did not know about the Function.bind method. My imaginary hat off to you! :) – ManiSto Feb 16 '12 at 17:47
  • Thank you. It's a new step in JS knowledge. BTW, do all browsers support `.bind` ? – Paul Jun 05 '13 at 16:53
  • Looks like I'm having the same problem here. Is there no other way to do this, save creating a global variable reference to the object? I'd prefer some trick that makes it work in all browsers. – krb686 Aug 14 '13 at 15:24
  • @krb686 the `Function.bind` shim ensures browser compatibility, or you can pass an anonymous function instead of `this.draw.bind(this)`, but that's not as clean. – Matt Ball Aug 14 '13 at 15:49
  • Which method (bind, global, closure) is the fastest / most efficient? – Hobbes Mar 09 '15 at 16:15
  • @MattBall :: I tried to use bind but its giving Maximum stack size exceeded error. – Manish Mahajan Jul 14 '15 at 11:43
34

Now that ES6/2015 is here, if you are using a transpiler then an arrow function has lexical this binding so instead of:

window.webkitRequestAnimationFrame(this.draw.bind(this));

you can do:

window.webkitRequestAnimationFrame(() => this.draw());

which is a bit cleaner.

I've used this effectively with Typescript transpiling to ES5.

James World
  • 29,019
  • 9
  • 86
  • 120
  • 1
    This is what I now use. – Ryan Jun 19 '16 at 12:03
  • 1
    I just used this technique, and I had to put an argument in the arrow function, corresponding to the timeStamp argument that the rAF() callback receives: `(t) => this.draw(t)` – Sideways S Sep 27 '18 at 20:04
6

I can't guarantee that this is a good idea and that I'm right, but running .bind on every requestAnimationFrame means creating a new function on every iteration. It just doesn't sound right to me.

That's why in my project I cached the bound function to avoid the anti-pattern.

Simple example:

var Game = function () {
    this.counter = 0;
    this.loop = function () {
        console.log(this.counter++); 
        requestAnimationFrame(this.loop);
    }.bind(this);
    this.loop();
}
var gameOne = new Game();

If you have a more complex project with prototype inheritance you can still create a cached function with "this" bound in object's constructor

var Game = function () {
    this.counter = 0;
    this.loopBound = this.loop.bind(this);
    this.loopBound();
}
Game.prototype.loop = function () {
    console.log(this.counter++); 
    requestAnimationFrame(this.loopBound);
}
var gameOne = new Game();

Thoughts? http://jsfiddle.net/3t9pboe8/ (look in the console)

Pawel
  • 16,093
  • 5
  • 70
  • 73
  • 1
    I modified your [fiddle](http://jsfiddle.net/uuvqju4L/1/) to (I think) test the difference between caching the bound function and not. I was surprised to see nearly identical performance between the two (in Firefox 47, Chrome 52, and IE 11). It appears as though the function created by `bind` is cached by the browser, but I don't know what's actually going on. I even tried running each individually to avoid interference from the browser's optimization of `requestAnimationFrame`. – insectean Aug 29 '16 at 22:48
  • 1
    @SeanH in this case "loop" is a really simple function consisting of a counter incrementation and one condition check. I wonder if using "bind" on every iteration of much more complex function would make a bigger difference. Probably the gain of this practice on requestAnimationFrame which never goes above 60fps is marginal. – Pawel Aug 30 '16 at 09:32
3

how about this:

Display.prototype.draw = function(){
  this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
  //Animation stuff here.

  window.webkitRequestAnimationFrame( $.proxy(function() {this.draw()}, this) );
};

...assuming you use jquery

bogdan
  • 1,269
  • 3
  • 12
  • 18
1

you have not to use "this". Keep it simple.

var game = {
      canvas:null,
      context:null,

    init:function(){
            // init canvas, context, etc
    },      

    update:function(){
        //do something
        game.render();                        
        requestAnimationFrame(game.update, game.canvas);        
    },            
};
Tomáš
  • 2,078
  • 3
  • 22
  • 21
  • Nice. I needed a method to be accessible from outside the class, so I used this idiom: `var update = this.update = function() { render(); requestAnimationFrame(update); }` – Jason Moore Nov 11 '13 at 14:55
1

Beside bind method, and arrow function solution (offered by Jamaes World's answer), Another (rather old) work around could be:

var self = this
window.webkitRequestAnimationFrame(
    function() {
        self.draw()
    }
);
MortezaE
  • 378
  • 3
  • 13
-2

And also you might want to use requestAnimationFrame shim to make it work on all browsers https://github.com/kof/animation-frame

Oleg Isonen
  • 1,463
  • 10
  • 10