JavaScript:在requestAnimationFrame循环中使用cancelAnimationFrame

时间:2014-08-19 05:10:58

标签: javascript prototype requestanimationframe

为了学习,我正在为所有HTMLElements创建一个动画函数,受jQuery的启发。动画启动很好,但是我希望它在requestAnimationFrame的time =函数中给出的duration之后停止。我在动画循环中使用cancelAnimationFrame,但它不会停止循环。

    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)
        }
    }

该函数就像这样调用

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

1 个答案:

答案 0 :(得分:2)

问题的核心在于,您只能获得第一个动画帧(var aniLoop = (...)行)的ID以及您正在尝试的内容取消 - 除了对requestAnimationFrame的每次通话都有不同的ID,因此您需要存储最后一次通话的返回值并取消它:

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);
    }
};

但是,您的代码还有其他几个问题需要解决,否则您的功能会彻底爆发:

:1 循环中的函数声明

我建议阅读differences between function declaration and expression以获得更好的图片,但问题是您在循环中执行函数声明,这被认为是未定义的行为(某些引擎将替换函数,有些人不会,有些人会爆炸)。鉴于动画只有单个持续时间,因此可能是同步的,它是迭代属性以在单个动画函数内部动画的更好选择,如下所示:

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 动画时间戳

从目前看来,你的动画函数可能不会运行多个帧 - 如果你看requestAnimationFrame documentation on MDN,你会注意到给requestAnimationFrame的回调是给定的时间戳,即从UNIX纪元(1970年1月1日)开始的毫秒数 - 因此time >= (duration * 1000)的条件将始终为真。而不是那样,注意关闭动画时的开始时间并将回调中的时间戳与它进行比较,如下所示:

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 动画限制

这个并不重要,但仍值得考虑 - requestAnimationFrame旨在被浏览器自动限制和管理,因此您不需要对动画是否应该应用自己的条件跑(无论如何,它不会超过60FPS,因为这是规格的上限)。相反,它应该简单地处理当前时间与开始时间的差异,以确保您的动画仍然在正确的位置,即使由于某种原因,动画中存在滞后:

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);
}