【问题标题】:Update progressbar in each loop在每个循环中更新进度条
【发布时间】:2015-09-08 08:00:20
【问题描述】:

我有一个进度条,我在多次迭代的循环中更新。

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

});

控制台按预期迭代更新。 但是,进度条没有更新。

问题是浏览器窗口似乎“挂起”,直到循环结束。 只更新控制台,不更新进度条。

我已尝试在多个位置添加 setTimeout,如下所示。 但这只会让事情变得更糟,因为我什至没有让控制台在执行循环时输出进度。

【问题讨论】:

  • 你的索引值的范围是多少?您是否打算除以 100 作为设置值的一部分?如果是这种情况,您可能会尝试使用十进制值设置进度条,但您已经解释过它可以处理 0 到 100 之间的整数。
  • 这只是一个简化。我可以在控制台输出中看到使用正确的值调用进度条。我已经用my_progressbar_element.innerHTML = 'update'; 替换了循环中的整个代码。仅在循环完成后才显示此文本。
  • 您的 div 的宽度正在更新为控制台值。只需在进度条 div 中添加 background-color:red 即可查看。
  • @blue 不,这不是问题所在。循环运行时,DOM 更新被阻止。我需要想办法解决这个问题。
  • 真实应用中进度更新如何?

标签: javascript jquery css progress


【解决方案1】:

好的,我在这个问题的答案中找到了解决方案:

Javascript: How to update a progress bar in a 'for' loop

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/

【讨论】:

  • 我做到了?好吧,我寻求的解决方案完全不同。它涉及到 jQuery 的 queue()、setTimeout() 和 dequeue()。
【解决方案2】:

让我们将其分解为步骤

第 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/

【讨论】:

    【解决方案3】:

    您真正想要的是允许浏览器在迭代之间更新 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;
    });
    

    【讨论】:

    • 我尝试了几种不同的方法来使用 setTimeout 和 setInterval,但这是唯一一种对我有用的方法,我有一个长时间运行的函数,其中包含多个要循环的大型数组和一个 ajax 调用。跨度>
    【解决方案4】:

    我的猜测是您的所有进度更新都在同一个调用堆栈中运行。当 JavaScript 代码运行时,DOM 无法更新。也许这个question 会帮助你想出一个解决方法。

    【讨论】:

    • 我之前听说过将 setTimeout 设置为零。这听起来像是一个合理的解决方案。但是当我按照这里的建议实现类似的东西 stackoverflow.com/a/5226333/426266 时,它不起作用。我仍然只在控制台中看到输出。循环完成后,DOM 会更新。
    【解决方案5】:

    你想做什么?你为什么需要它?当您必须等待某事完成时,您应该只使用进度条。但我们不知道您在页面上做了什么。

    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. 对于大范围的过程,例如数学或附加大量需要 10 多秒的 div:

      Main.js:

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

      Worker.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);
      

    【讨论】:

      【解决方案6】:

      您可以使用promises 等到宽度设置好后再继续循环。
      如果您从 1 到 1 更新 1000000000 次迭代的进度条会很慢,因此您可能会发现降低更新频率很有用。
      我没有使用for 循环,而是使用了一个递归函数,该函数在实现承诺时循环。

          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/

      【讨论】:

        【解决方案7】:

        你必须使用window.requestAnimationFrame,否则浏览器会阻塞直到你的循环结束。传递给requestAnimationFrame 的回调将获得一个时间戳作为参数,您可以将其用于计算进度。

        【讨论】:

          【解决方案8】:

          这是我对这个问题的两种看法:

          使用web worker。 webworker blob代码来自here

          网络工作者代码:

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

          other idea 挂起 UI 线程,但在视觉上改变了宽度,因为我使用 requestAnimationFrame 中断计数,更改进度条的宽度,然后继续计数。

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

          【讨论】:

            【解决方案9】:

            也许这会有用。

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

            【讨论】:

            • 请在您的回答中使用标准英语。如果您从其他来源复制,请努力将其转换为英文并添加对该来源的引用
            • 我们可以纠正坏掉的英语。就试一试吧。我的也不好:)
            【解决方案10】:

            这里是更新的小提琴

            我使用 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();
            });
            

            Fiddler

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

            【讨论】:

            • 因此,与其链接到外部小提琴(您也可以这样做),不如在此处列出一些代码并至少解释相关部分。如果你真的认为你有一个成功的答案,那么在链接消失之前让它成为值得其他人发现的东西。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-07-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多