How to use requestAnimationFrame with a TypeScript object?
Asked Answered
C

4

13

I have an object that i want to do drawing on a canvas. It will use requestAnimationFrame to start a game loop:

Contoso.ts

class Contoso
{
   //private ctx: CanvasRenderingContext2D;

   Initialize(ctx: CanvasRenderingContext2D) {
      //this.ctx = ctx;
      Render();
   }

   Render() {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(this.Render);
   }
}

app.ts

var contoso: Contoso;

contoso = new Contoso();
contoso.Initialize(canvas);

The first time someone calls Initialize, the requestAnimationFrame manages to correctly call Render.

The second time requestAnimationFrame calls Render, the this.Render is undefined and it crashes.

It's almost as though the object was destroyed after the initial call to Initialize.

What is going on?

Catafalque answered 21/2, 2014 at 3:28 Comment(1)
A video on how to maintain this : youtube.com/watch?v=tvocUcbCupA&hd=1Snip
C
46

You've lost this context. Two possible fixes:

class Contoso
{
   /* ... */

   // Use () => syntax so Render always gets 'this' context
   // from the class instance
   Render = () => {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(this.Render);
   }
}

The alternate fix is probably slightly clearer, but has the downside of making a lot more allocations (you probably don't want to allocate 1 closure per frame!)

   Render() {
      //...snip doing any actual drawing for the purpose of this question
      requestAnimationFrame(() => this.Render);
   }
Capping answered 21/2, 2014 at 3:33 Comment(3)
At least I know I'm not crazy - people have had this problem before me. On the other hand, the fixed versions smell enough that I'm probably not supposed to have a class maintain the render loop. I am probably supposed to have global variables invoke the a Render method on that global variableCatafalque
@Ryan: First suggestion (i.e. Render = () => { ... }) works nicely. Thanks.Gnathonic
is there any need of cancelling the animation frame for the performance? if yes then please let me know how can I do it?Incomprehension
P
8

Use arrow syntax (lambda):

requestAnimationFrame(() => this.Render());
Post answered 21/2, 2014 at 3:32 Comment(1)
Yeah for some reason this does not work. The Render function does not even get called.Undertaking
S
4

The best approach I've found.

requestAnimationFrame(this.Render.bind(this));

.bind(this) creates a new function that has its this keyword set to the provided value.

Bonus Reading

Syllabogram answered 24/8, 2017 at 20:48 Comment(3)
using bind is dangerous: basarat.gitbooks.io/typescript/docs/tips/bind.htmlAzerbaijan
Can you expand on bind's dangers, @gregmatys? The page you linked to doesn't exist at that URL any more, and a search on the site didn't (quickly) show up anything explicitly arising against binding class methods. When writing class-based React code in JavaScript, the this.fn = this.fn.bind(this) pattern is very common. I assume many people will encounter problems like this moving to TypeScript, so more explanation here would help.Amidase
hamednourhani.gitbooks.io/typescript-book/docs/tips/bind.htmlAzerbaijan
S
2

On Firefox 49.0.1 I've got an error message using Ryan Cavanaugh solution.

SyntaxError: bad method definition

for the line :

Render = ()=> {

The work around I've found looks like this :

class Test{

    constructor(){

        this.Render = ()=> {
            requestAnimationFrame( this.Render );
        };

    }
}
Scansorial answered 29/9, 2016 at 10:21 Comment(1)
Try it with Render = () => { instead of this.Render = () => {. To be clear, I am referring to @Ryan's code, outside of a constructor.Skvorak

© 2022 - 2024 — McMap. All rights reserved.