Обновить progressbar в каждом цикле

У меня есть индикатор выполнения, который я обновляю в цикле многих итераций.

https://jsfiddle.net/k29qy0do/32/ (откройте консоль, прежде чем нажать кнопку Пуск)

var progressbar = {};

$(function () {

    progressbar = {

        /** initial progress */
        progress: 0,

        /** maximum width of progressbar */
        progress_max: 0,

        /** The inner element of the progressbar (filled box). */
        $progress_bar: $('#progressbar'),

        /** Set the progressbar */
        set: function (num) {
            if (this.progress_max && num) {
                this.progress = num / this.progress_max * 100;
                console.log('percent: ' + this.progress + '% - ' + num + '/' + this.progress_max);

                this.$progress_bar.width(String(this.progress) + '%');
            }
        },

        fn_wrap: function (num) {
            setTimeout(function() {
                this.set(num);
            }, 0);
        }

    };

});


$('#start_button').on('click', function () {

    var iterations = 1000000000;

    progressbar.progress_max = iterations;

    var loop = function () {

        for (var i = 1; i <= iterations; i++) {

            if (iterations % i === 100) {

                progressbar.set(i); //only updates the progressbar in the last iteration

                //progressbar.fn_wrap(i); //even worse, since no output to the console is produced

            }
        }
    }

    //setTimeout(loop, 0);
    loop();

});

консоль обновляется итеративно, как и ожидалось. Однако progressbar не обновляется.

проблема в том, что окно браузера, кажется, "зависает", пока цикл не закончится. Обновляется только консоль, а не progressbar.

Я попытался добавить setTimeout, как предлагается ниже, в нескольких местах. Но это только ухудшает ситуацию, потому что я даже не получаю консоль для вывода прогресса при выполнении цикла.

10 ответов


хорошо, я нашел решение в ответе на этот вопрос:

Javascript: как обновить индикатор выполнения в цикле "for"

var i = 0;
(function loop() {
    i++;
    if (iterations % i === 100) {
        progressbar.set(i); //updates the progressbar, even in loop    
    }   
    if (i < iterations) {
        setTimeout(loop, 0);
    }
})();

мое решение: https://jsfiddle.net/ccvs4rer/3/


Я предполагаю, что все ваши обновления прогресса выполняются в одном стеке вызовов. Во время выполнения кода JavaScript DOM не может обновляться. Может быть, это вопрос поможет вам придумать обходной путь.


то, что вы действительно хотите, - это асинхронный цикл, позволяющий браузеру обновлять DOM между итерациями.

JSFiddle: http://jsfiddle.net/u5b6gr1w/

function delayedLoop(collection, delay, callback, context) {
    context = context || null;

    var i = 0,
        nextInteration = function() {
            if (i === collection.length) {
                return;
            }

            callback.call(context, collection[i], i);
            i++;
            setTimeout(nextInteration, delay);
        };

    nextInteration();
}

некоторые HTML:

<div class="progress-bar"><div style="width: 0"></div></div>

всплеск CSS:

.progress-bar {
    border: 1px solid black;
    background-color: #f0f0f0;
}
.progress-bar div {
    background-color: red;
    height: 1.25em;
}

и некоторые JavaScript, чтобы связать вещи вместе:

var progressBar = document.querySelector(".progress-bar div"),
    items = [1,2,3,4,5,6,7,8,9,10];

delayedLoop(items, 500, function(item, index) {
    var width = (item / items.length * 100) + "%";
    progressBar.style.width = width;
    progressBar.innerHTML = width;
});

давайте разбить это на шаги

Шаг 1: Очистка HTML

предполагая, что цель вашего вопроса состоит в том, чтобы понять, как работать индикатор выполнения, а не стили или метки (загрузка, пожалуйста, будьте терпеливы и т. д.). Давайте просто иметь индикатор выполнения и кнопку "Пуск".

<div id='progressbar-outer' style="">
    <div id='progressbar' style=""></div>
</div>
<button id="start_button">Start</button>

Шаг 2: Стили

позволяет сделать индикатор выполнения видимым для пользователя

#progressbar-outer {
    height:2em;
    border:5px solid #000;
    width:15em;
}
#progressbar {
    width:0%;
    background-color:#F00;
    height:100%;
}

Шаг 3: Используя setTimeout где принадлежит

в вашем коде вы использовали setTimeout чтобы установить значение индикатора выполнения. Однако for цикл все еще активен.

for (var i = 1; i <= iterations; i++) {

    if (iterations % i === 100) {

        progressbar.set(i); //only updates the progressbar in the last iteration

        //progressbar.fn_wrap(i); //even worse, since no output to the console is produced

        //setTimeout(function() {
        //  progressbar.set(i);
        //}, 0);

    }
}

использование setTimeout не влияет на остальной код. Следовательно, UI был заложником до конца цикла. Попробуйте следующий код.

$('#start_button').on('click', function () {

    var iterations = 100;

    progressbar.progress_max = iterations;

    var loop = function (value) {
        progressbar.set(value);
        if (value < iterations) setTimeout(function () {
            loop(value + 1)
        }, 30);
        else $('#progressbar').css('background-color', '#0F0');
    }


    loop(1);

});

предварительный просмотр

попробуйте эту скрипку: https://jsfiddle.net/Ljc3b6rn/4/


что вы wnat сделать? Зачем она тебе? Вы должны использовать progressbar только тогда, когда вам нужно ждать чего-то, чтобы закончить. Но мы не знаем, что вы делаете на Вашей странице.

  1. если вы хотите отобразить ход загрузки ajax:

    $.ajax({
        ...
        xhr: function() {
            var xhr = $.ajaxSettings.xhr();
            $(xhr.upload).bind("progress", function(event) {
                var e = event.originalEvent;
                var percent = 0;
                if (e.lengthComputable)
                    percent = Math.ceil(e.loaded/e.total*100);
                $("#progress").width(percent+"%");
            });
            return xhr;
        }
        ...
    });
    
  2. для изображений вам нужен вызов ajax:

    $.ajax({
        method: "GET",
        url: "http://example.com/path/image.jpg",
        xhr: function() {/* see the code above*/ }
        ...
    });
    
  3. для получения содержимого загруженного файла:

    var reader = new FileReader();
    reader.readAsText(uploadedFile);
    $(reader).bind("progress", function(e) {
        var percent = 0;
        if (e.lengthComputable)
            percent = Math.ceil(e.loaded/e.total*100);
        $("#progress").css("width", percent+"%");
    });
    
  4. для большого вокруг процесса, как математика или добавление много divs, что займет 10 + секунд:

    Main.js:

    var worker = new Worker("Worker.js");
    $(worker).bind("message", function(data) {
        $("#progress").width((data*100)+"%");
    });
    

    работника.js:

    var total = 43483,
        finished = 0,
        doStuff = function() {
            ++finished;
            return 1+1;
        };
    setInterval(function()
    {
        self.postMessage(finished/total);
    }, 100);
    for (var i = 0; i < total; ++i)
        setTimeout(doStuff, i*10);
    
  5. потому что это приятно, и вы хотите сказать пользователю, что есть прогресс, когда его нет, просто анимируйте div:

    $("#progress").animate({width: "100%"}, 3000);
    

можно использовать promises чтобы дождаться установки ширины, прежде чем продолжить цикл.
Обновление индикатора выполнения для 1000000000 итераций будет медленным, если вы идете 1 на 1, поэтому вам может быть полезно уменьшить частоту обновления.
Вместо for loop, я использовал рекурсивную функцию, которая выполняет циклы, когда обещание было выполнено.

    set: function (num) { 
        var deferred = $.Deferred(); 
        if (this.progress_max && num) {
            this.progress = num / this.progress_max * 100;
            var self = this; 
            self.$progress_bar.animate({"width": String(this.progress) + '%'}, "fast", function() {  
                deferred.resolve(); 
            }); 
            return deferred; 
        }
    }

$('#start_button').on('click', function () {

    var iterations = 1000000000;
    var i = 0; 
    progressbar.progress_max = iterations;

    var loop = function(){
        i+=100000000; 
        if(i <= iterations){
            progressbar.set(i).then(function(){ 
                loop(); 
            }); ;         
        }
    }; 

    loop();
});

https://jsfiddle.net/k29qy0do/34/


вы должны использовать window.requestAnimationFrame, в противном случае браузер будет блокировать, пока ваш цикл не будет завершен. Обратный вызов перешел в requestAnimationFrame получит метку времени в качестве параметра, который вы можете использовать для вычислений прогресса.


Это мои 2 берет на себя вопрос:

С помощью веб-работника. Код blob webworker происходит от здесь

работник веб-код:

<script type="text/ww">    
    function loop(e) {
        var data = JSON.parse(e.data);
        var i = parseInt(data.i, 10);
        var iterations = parseInt(data.iterations, 10);

        while (iterations % ++i !== 100 && i <= iterations);

        if(i <= iterations) {
            self.postMessage(JSON.stringify({ i: i, iterations: iterations }));
        }
    }

    self.onmessage = function(e) {
        loop(e);
    };
</script>

код:

var ww = document.querySelector('script[type="text/ww"]'),
    code = ww.textContent,
    blob = new Blob([code], {type: 'text/javascript'}),
    blobUrl = URL.createObjectURL(blob),
    worker = new Worker(blobUrl);

worker.onmessage = function(e) {
    var data = JSON.parse(e.data);
    var i = parseInt(data.i, 10);
    var iterations = parseInt(data.iterations, 10);

    progressbar.set(i);

    worker.postMessage(JSON.stringify({ i: i, iterations: iterations }));
}

$('#start_button').on('click', function () {

    var iterations = 1000000000;

    progressbar.progress_max = iterations;

    worker.postMessage(JSON.stringify({ i: 0, iterations: iterations }));
});

на другая идея зависает поток пользовательского интерфейса, но визуально изменяет ширину, поскольку я использую requestAnimationFrame, чтобы сломать подсчет, изменить ширину progressbar, а затем продолжить подсчет.

function loopFrame(i, iterations) {
    requestAnimationFrame(function() {
        if (iterations % i === 100) {
            progressbar.set(i);
        }

        if(i < iterations) {
            loopFrame(i + 1, iterations);
        }
    });
}

$('#start_button').on('click', function () {
    var iterations = 1000000000;

    console.log(iterations);

    progressbar.progress_max = iterations;

    loopFrame(0, iterations);

});

возможно, это будет полезно.

var service = new Object();

//function with interrupt for show progress of operations
service.progressWhile = new Object();
service.progressWhile.dTime = 50; //step ms between callback display function
service.progressWhile.i = 0; //index
service.progressWhile.timer = 0; //start time for cycle

//@parametr arr - array for actions
//@parametr actionCallback - The function for processing array's elements
//@parametr progressCallback - function to display the array index

function progressWhile(arr, actionCallback, progressCallback) {
    try {
        var d = new Date();
        service.progressWhile.timer = d.getTime();
        log(service.progressWhile.i);
        if (service.progressWhile.i >= arr.length) {
            service.progressWhile.i = 0;
            return;
        }

        while (service.progressWhile.i < arr.length) {
            actionCallback(arr[service.progressWhile.i++]);
            d = new Date();
            if (d.getTime() - service.progressWhile.timer > service.progressWhile.dTime) {
                break;
            }
        }
        if (progressCallback != undefined)
            progressCallback(service.progressWhile.i);
    } catch (er) {
        log(er);
        return;
    }

    setTimeout(function () {
        progressWhile(arr, actionCallback, progressCallback);
    }, 0);
}

вот обновленная скрипка

я использовал animate, чтобы сделать его индикатором прогресса, как внешний вид. Надеюсь, это поможет вам.

var progressbar = {};

$(function() {

    progressbar = {
        /** initial progress */
        progress : 0,
        /** maximum width of progressbar */
        progress_max : 0,
        /** The inner element of the progressbar (filled box). */
        $progress_bar : $('#progressbar'),
        /** Method to set the progressbar.*/
        set : function(num) {
            if (this.progress_max && num) {
                this.progress = num / this.progress_max * 100;
                console.log('percent: ' + this.progress + '% - ' + num + '/' + this.progress_max);

                $('#progressbar').animate({
                    width : String(this.progress) + '%',
                }, 500, function() {
                    // Animation complete.
                });
            }
        },

        fn_wrap : function(num) {
            setTimeout(function() {
                this.set(num);
            }, 0);
        }
    };

});

$('#start_button').on('click', function() {
    $('#progressbar').css('width', '0%');
    var iterations = 1000000000;
    progressbar.progress_max = iterations;
    var loop = function() {
        for (var i = 1; i <= iterations; i++) {
            if (iterations % i === 100) {
                progressbar.set(i);
                //only updates the progressbar in the last iteration
            }
        }
    }
    loop();
});

Саша

  [1]: https://jsfiddle.net/k29qy0do/21/