简体   繁体   中英

JavaScript: using cancelAnimationFrame inside requestAnimationFrame loop

For the sake of learning I am prototyping an animate function for all HTMLElements, inspired by jQuery. The animation starts up just fine, but I want it to stop after the requestAnimationFrame's time = the duration given in the function. I am using cancelAnimationFrame inside the animation loop, but it doesn't stop the loop.

    HTMLElement.prototype.animate = function(properties,duration){

        for(prop in properties){

            var last = 0,
                fps = 60;

            function ani(time){

                requestAnimationFrame(ani);
                if ((time - last) > fps ){                       
                    last = time
                    console.log(time)
                    if(time >= (duration*1000)){
                        window.cancelAnimationFrame(aniLoop)
                    }    
                }
            }
            var aniLoop = requestAnimationFrame(ani)
        }
    }

The function is called like this

    c.animate({"property":"value"},1)

At the core of the problem lies that fact that you're only getting the ID of the first animation frame (the var aniLoop = (...) line) and that's what you're trying to cancel - except that each call to requestAnimationFrame has a different ID, thus you'd need to store the return value of the last call and cancel that instead:

HTMLElement.prototype.animate = function(properties,duration) {
    "use strict";

    var aniLoop,
        prop,
        last = 0,
        fps = 60;

    for (prop in properties) {

        function ani(time) {

            aniLoop = requestAnimationFrame(ani);
            if ((time - last) > fps) {                       
                last = time;
                console.log(time);
                if (time >= (duration * 1000)) {
                    cancelAnimationFrame(aniLoop);
                }    
            }
        }
        aniLoop = requestAnimationFrame(ani);
    }
};

There are, however, several other problems with your code that need to be tackled as well, otherwise your function will blow up rather thoroughly:

:1 Function declaration in a loop

I would recommend reading about differences between function declaration and expression to get a better picture, but the problem here is that you're doing function declaration in a loop, which is considered undefined behaviour (some engines will replace the functions, some will not, some will blow up). Given that the animations have only single duration given and thus, are probably synchronised, it'd be a better option to iterate over properties to animate inside of a single animation function, like so:

HTMLElement.prototype.animate = function(properties,duration) {
    "use strict";

    var aniLoop,
        last = 0,
        fps = 60;

    function ani(time) {

        var prop;

        aniLoop = requestAnimationFrame(ani);
        if ((time - last) > fps) {

            last = time;

            for (prop in properties) {
                console.log(prop + ': ' + time);
            }

            if (time >= (duration * 1000)) {
                cancelAnimationFrame(aniLoop);
            }    
        }
    }
    aniLoop = requestAnimationFrame(ani);
}

:2 Animation timestamping

As it looks currently, your animation function will probably not run more than one frame anyway - if you look at requestAnimationFrame documentation on MDN , you'll notice that the callback given to requestAnimationFrame is given a timestamp, ie value in milliseconds from the beginning of UNIX epoch (1st of January 1970) - therefore the condition of time >= (duration * 1000) will always be true. Instead of that, register the starting time when you kick the animation off and compare the timestamp within the callback to it, like so:

HTMLElement.prototype.animate = function(properties,duration) {
    "use strict";

    var aniLoop,
        start,
        last = 0,
        fps = 60;

    function ani(time) {

        var prop,
            progress;

        aniLoop = requestAnimationFrame(ani);
        if ((time - last) > fps) {

            last = time;
            progress = time - start;

            for (prop in properties) {
                console.log(prop + ': ' + progress + ' out of ' + (duration * 1000));
            }

            // This is where we get a difference between current and starting time
            if (progress >= (duration * 1000)) {
                cancelAnimationFrame(aniLoop);
            }    
        }
    }
    start = Date.now();
    aniLoop = requestAnimationFrame(ani);
}

:3 Animation throttling

This one is not as crucial, but still worth a consideration - requestAnimationFrame is intended to be automatically throttled and regulated by the browser, thus you don't need to apply your own conditions on whether animation should run (it won't go over 60FPS anyway, as that's the specification's ceiling). Instead, it should simply work on difference of current time from starting time, to make sure your animation still ends up in correct place even if for some reason, there is a lag in animation:

HTMLElement.prototype.animate = function(properties,duration) {
    "use strict";

    var aniLoop,
        start;

    function ani(time) {

        var prop,
            progress;

        aniLoop = requestAnimationFrame(ani);
        progress = time - start;

        for (prop in properties) {
            console.log(prop + ': ' + progress + ' out of ' + (duration * 1000));
        }

        // This is where we get a difference between current and starting time
        if (progress >= (duration * 1000)) {
            cancelAnimationFrame(aniLoop);
        }    
    }
    start = Date.now();
    aniLoop = requestAnimationFrame(ani);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM