javascriptthisrequestanimationframenative-methods

javascript, assigning requestAnimationFrame & cancelAnimationFrame to properties of an object


//
// version 1
//
var requestAnimFram = (function(){
    return window.requestAnimationFrame    ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        window.oRequestAnimationFrame      ||
        window.msRequsetAnimationFrame     ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        }
})();

//version 1 usage
function main(){
    //main loop
    ...
    requestAnimFram(main);
}
main();

//
// version 2
//
var animFram = {
    req: window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback){
            window.setTimeout(callback, 1000 / 60);
        },
    ccl: window.cancelAnimationFrame ||
        window.mozCancelAnimationFrame,
    myReq: 0
};

//version 2 usage
function main(){
    ...
    aniFram.myReq = aniFram.req(main);
}
main();

function stop(){
    aniFram.ccl(myReq);
}

While I was exploring some example codes, I found requestAnimationFrame. version 1 is carved from that and it works fine. After seaching for a while, I found cancelAnimationFrame as well and wanted to use both of them. So I made a dummy page for test. The version 2 is carved from it.

The problem is that, it doesn't loop. So, I have two questions.

  1. Is it impossible to use requestAnimationFrame in this way? If so, why exactly so?

  2. If it's possible-but I'm doing it in wrong way, how can I acheive this?


Solution

  • This is a duplicate("Uncaught TypeError: Illegal invocation" in Chrome). But I'm still gonna answer my own question with more detail, as such could help others to understand this matter in a different way.

    1. Is it impossible to use requestAnimationFrame in this way? If so, why exactly so?

    It is impossible.

    1. If it's possible-but I'm doing it in wrong way, how can I acheive this?

    This problem, by itself, can be easily fixed by using call() and bind() and apply() methods.

    //Using call()
    //Attach call method during invocation
    aniFram.req.call(window, main);
    
    //Using bind()
    //Attach bind method during the object initialization
    aniFram = {
        req: requestAnimationFrame.bind(window)
        ...
    }
    aniFram.req(main);
    
    //Using apply()
    //Attach apply method during invocation
    aniFram.req.apply(window, [main]);
    

    Notice the similarity in here that all 3 methods somehow have an additional parameter 'window'. They all have a same reason for it: requestAnimationFrame is a method of window object that requires the context of window.

    aniFram is an object. It has a method req which references the window.requestAnimationFrame. aniFram.req(main) invokes the window.requestAnimationFrame in the context of the aniFram, not window. That's why it doesn't work. Let's consider another example code:

    Example Code

    var obj1 = {
        target: 'Jake',
        hitman: function(){
            this.target = 'RIP';
        }
    };
    var obj2 = {
        //assigns obj1.hitman to obj2.hitman
        hitman: obj1.hitman
    };  
    obj2.hitman();
    console.log(obj1.target); //outputs 'Jake'
    
    /////////////////////////////////////////
    //call() method
    obj2.hitman.call(obj1);
    console.log(obj1.target); //outputs 'RIP'
    
    //apply() method
    obj2.hitman.apply(obj1);
    console.log(obj1.target); //outputs 'RIP'
    
    //bind() method
    var obj2 = {
        hitman: obj1.hitman.bind(obj1)
    };
    obj2.hitman();
    console.log(obj1.target); //outputs 'RIP'
    

    This is exactly same situation as your code, version 2. You invoke obj2.hitman() that references obj1.hitman expecting to change the value of obj1.target, but it does nothing. Because what obj1.hitman does is executing a statement this.target = 'RIP'. Since it is executed in the context of obj2, this statement becomes obj2.target = 'RIP'. There is no target property in obj2.

    call, apply, bind

    That's where these methods kick in. Without them, Javascript Engine determines context automatically (current object. ie. aniFram, obj2). By attaching these methods to your code, now you can decide in which context it will be executed (ie. window, obj1).


    This is also called as an Alias Function. (If Javascript has first-class functions, why doesn't this work?)