requestAnimationFrame with this keyword
Asked Answered
B

7

60

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);
};
Behistun answered 19/5, 2011 at 21:39 Comment(0)
R
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));
};
Rarely answered 19/5, 2011 at 21:45 Comment(10)
I'm trying to pass display.draw which is the function in which webkitRequestAnimationFram resides.Behistun
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).Rarely
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'Behistun
Hmm, still doesn't work Uncaught TypeError: Object #<Display> has no method 'fn' but don't worry about it, the bind method is good enough. Thanks for your help.Behistun
I did not know about the Function.bind method. My imaginary hat off to you! :)Environ
Thank you. It's a new step in JS knowledge. BTW, do all browsers support .bind ?Butterflies
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.Outport
@Outport 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.Rarely
Which method (bind, global, closure) is the fastest / most efficient?Sac
@MattBall :: I tried to use bind but its giving Maximum stack size exceeded error.Ruble
M
36

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.

Mozzarella answered 12/6, 2016 at 10:41 Comment(2)
This is what I now use.Behistun
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)Punner
E
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)

Estaminet answered 29/9, 2015 at 1:45 Comment(2)
I modified your fiddle 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.Shrum
@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.Estaminet
M
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

Meeting answered 1/8, 2012 at 5:10 Comment(1)
How to use it without jquery?? I tried to use bind but its giving Maximum stack size exceeded error.Ruble
P
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);        
    },            
};
Persse answered 11/4, 2013 at 8:11 Comment(1)
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); }Gentry
C
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()
    }
);
Costin answered 19/8, 2018 at 12:55 Comment(0)
F
-2

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

Fayalite answered 31/7, 2014 at 20:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.