JavaScript: использование cancelAnimationFrame внутри цикла requestAnimationFrame

Ради обучения я создаю прототип функции анимации для всех элементов HTML, вдохновленных 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)

person Christopher Reid    schedule 19.08.2014    source источник


Ответы (1)


В основе проблемы лежит тот факт, что вы получаете идентификатор только первого кадра анимации (строка var aniLoop = (...)), и это то, что вы пытаетесь отменить, за исключением того, что каждый вызов requestAnimationFrame имеет другой идентификатор, поэтому вы 'должен сохранить возвращаемое значение последнего вызова и вместо этого отменить его:

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 Объявление функции в цикле

Я бы рекомендовал прочитать о различиях между функциями объявление и выражение, чтобы получить лучшую картину, но проблема здесь в том, что вы выполняете объявление функции в цикле, что считается неопределенным поведением (некоторые движки заменят функции, некоторые нет, некоторые взорвут ). Учитывая, что анимациям задана только одна продолжительность и, следовательно, они, вероятно, синхронизированы, было бы лучше перебирать свойства для анимации внутри одной функции анимации, например так:

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 на MDN, вы заметите, что обратный вызов, переданный requestAnimationFrame, получает метку времени, т. е. значение в миллисекундах с начала эпохи UNIX (1 января 1970 г.) — поэтому условие 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 предназначен для автоматического дросселирования и регулирования браузером, поэтому вам не нужно применять свои собственные условия относительно того, должна ли запускаться анимация (она не будет превышать 60 кадров в секунду). во всяком случае, так как это потолок спецификации). Вместо этого он должен просто работать с разницей текущего времени со временем начала, чтобы убедиться, что ваша анимация все еще заканчивается в правильном месте, даже если по какой-то причине в анимации есть задержка:

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);
}
person D1SoveR    schedule 19.08.2014
comment
самая большая проблема всех возиться с HTMLElement.prototype, это плохая практика, и нет особой причины делать это исправление обезьяны. - person mpm; 19.08.2014