简体   繁体   English

即使我尝试了很多方法来强制重绘,进度条也不会在繁重的工作中重绘

[英]Progress bar wouldn't repaint during heavy work, even though I've tried many methods to force repaint

I have a JavaScript method that iterates over ~200 inputs, inserts values, and triggers onchange events on them. 我有一个JavaScript方法,可以迭代200多个输入,插入值并触发它们的onchange事件。 That takes about 2000 ms in Chrome and 10000 ms in IE. 在Chrome中大约需要2000毫秒,在IE中大约需要10000毫秒。

I want to make a progress bar that will show the progress. 我想制作一个进度条来显示进度。

Now, the issue is that browser wouldn't repaint the progress bar until the very end of the process, when it would immediately reach 100%. 现在的问题是,浏览器直到过程结束时才立即重绘进度条,此时它将立即达到100%。

After researching here, I've found three methods and tried all of them, but they don't work: 在这里研究之后,我发现了三种方法并尝试了所有方法,但是它们不起作用:

  • Run the progress bar update in a setTimeout with a zero or non-zero (eg 200 ms) timeout 在setTimeout中以零或非零(例如200 ms)超时运行进度条更新
  • Access the height property of the element or adjacent elements, which should force the repaint 访问元素或相邻元素的height属性,这将强制重新绘制
  • Show/hide the element and access the offsetHeight property in the meantime. 同时显示/隐藏元素并访问offsetHeight属性。

Here is my code: 这是我的代码:

    <div class="cg-progress-bar">
        <div class="cg-progress-bar-completed">
            &nbsp;
        </div>
        <div class="cg-inline-block cg-progress-bar-force-repaint">
            &nbsp;
        </div>
    </div>

JavaScript: JavaScript的:

var progressBarCurrent = 0;
var progressBarTotal = 236;

$.each(sqlData, function(column, value){
        //doing some work here to update values in inputs

        //update progress bar
        progressBarCurrent++;
        if (progressBarCurrent % 22 === 0) { //don't call too often
            var percentageComplete = Math.floor( (progressBarCurrent/progressBarTotal)*100 ) + "%";    

            var bar = $(".cg-progress-bar-completed").width(percentageComplete)[0];

            //hack 1
            bar.style.display = 'none';
            bar.offsetHeight;
            bar.style.display = '';

            //hack 2
            setTimeout(function() { bar.style.display = 'block'}, 0);

            //hack 3
            $(".cg-progress-bar-completed").animate({ width: percentageComplete }, 100).height();

            //hack 4 - insert empty text node
            bar.appendChild(document.createTextNode(' '));
            $(bar).hide().show(0);
            bar.appendChild(document.createTextNode(' '));

            //hack 5 - nuclear option
            $(window).trigger("resize");
        }
    }
});

How do I force the browser to repaint? 如何强制浏览器重新粉刷?

Probably your process is too heavy and is collapsing the execution thread. 可能您的进程太重,正在崩溃执行线程。 This means that while it's calculating the browser freezes slightly, and probably no other interaction with the UI can be made while calculations are processed. 这意味着在进行计算时,浏览器会稍微冻结,并且在处理计算时可能无法与UI进行其他交互。

To avoid this situations, HTML5 provides web workers technology. 为了避免这种情况,HTML5提供了Web Worker技术。 This is a way to emulate multitasking in browser environments, and allow you to execute heavy tasks avoiding browser freezing. 这是一种在浏览器环境中模拟多任务的方法,可让您执行繁重的任务以避免浏览器冻结。

Here you have an excellent introductory article to this technology, wich I've succesfully used to create a file uploader capable to upload Gb length files maintaining a live progress bar: 在这里,您会获得关于该技术的出色介绍性文章,直到我成功地使用它创建了一个文件上传器,该文件上传器能够上传Gb长度的文件并保持实时进度条:

https://www.html5rocks.com/en/tutorials/workers/basics/ https://www.html5rocks.com/en/tutorials/workers/basics/

I hope it helps. 希望对您有所帮助。

Ok so my previous anwser didn't work as I hoped so here is a working example: 好的,所以我以前的答案没有按我希望的那样工作,所以这是一个有效的示例:

index.html 的index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>-</title>
    <style type="text/css" media="screen">
        div{
            margin: 0px;
            padding: 0px;
            height: 30px;
        }
        #wrapper{
            width: 300px;
            border: solid 1px black;
        }
        #progressBar{
            width: 0px;
            background-color: black;
            color: grey;
        }
    </style>
</head>
<body>
    <div id="wrapper"><div id="progressBar"></div></div>
    <script type="text/javascript">

        var progressBar = document.getElementById("progressBar")    
        var workerThread = new Worker('worker.js');

        function drawProgress(progress){
            var percentageComplete = Math.floor( (progress.current/progress.maximum)*100 ) + "%";
            progressBar.style.setProperty("width",percentageComplete)
        }

        workerThread.onmessage = function(e){
            drawProgress(e.data)
        }
    </script>
</body>
</html>

worker.js worker.js

var sqlData = new Array( 236 );
var i = 0;
for ( let entry of sqlData ) {
    console.log( 'iteration of each' );
    postMessage( {
        current: i,
        maximum: sqlData.length
    } )
    for ( var n = 0; n < 200000; n++ ) {
        for ( let entry of sqlData ) {
            entry = Math.random()
        }
    }
    i++;
}

I think that this could be the best option, because there is no hack involved it is just the way for this kind of task. 我认为这可能是最好的选择,因为不涉及黑客,这只是完成此类任务的方式。 The only problem is it is kind of hard to get JQuery in the worker. 唯一的问题是,很难在工作程序中获取JQuery。 Best way there is to retrieve the SQL dataset completely in the main thread and then transmit is into the worker. 最好的方法是在主线程中完全检索SQL数据集,然后将其传输到工作线程中。

@K48 added it should be compatible with IE9+, so here is the solution with IE8+ support: @ K48添加了它应该与IE9 +兼容,因此这是支持IE8 +的解决方案:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>IE8 compatible</title>
    <style type="text/css">
        div{
            margin: 0px;
            padding: 0px;
            height: 30px;
        }
        #wrapper{
            width: 300px;
            border: solid 1px black;
        }
        #progressBar{
            width: 0px;
            background-color: black;
            color: grey;
        }
    </style>
</head>
<body>
    <div id="wrapper"><div id="progressBar"></div></div>
    <script type="text/javascript">
        var continueExecution = false;
        var progressBar = document.getElementById( "progressBar" );
        var i = 0;
        var sqlData = new Array( 236 );
        var lastDraw = (new Date).valueOf();

        function heavyloadFunction() {
            var entry = sqlData[ i ]

            // do something with the entry
            for ( var n = 0; n < 2000; n++ ) {
                for ( var h = 0; h < sqlData.length; h++ ) {
                    var cEntry = sqlData[h]
                    cEntry = Math.random()
                }
            }

            // finish entry work

            if(i < sqlData.length){
                i++;
                continueExecution = true;
            }else{
                console.log("finished")
                continueExecution = false;
            }
        }

        function drawProgress( progress ) {
            var percentageComplete = Math.floor( ( progress.current / progress.maximum ) * 100 ) + "%";
            progressBar.style.width = percentageComplete
        }

        function shouldReDraw(){
            var dNow = (new Date).valueOf();
            if(dNow - lastDraw > 16){
                // around 60fps
                lastDraw = dNow;
                return true;
            }else{
                return false;
            }
        }

        function excutionLoop() {
            heavyloadFunction();

            if ( continueExecution ) {
                if(shouldReDraw()){
                    drawProgress({
                        current: i,
                        maximum: sqlData.length
                    })

                    window.setTimeout( excutionLoop, 0 )
                }else{
                    excutionLoop()
                }
            }else{
                drawProgress({
                    current: i,
                    maximum: sqlData.length
                })
            }           
        }

        excutionLoop();
    </script>
</body>
</html>

The idea is quite simple, you interrupt the execution every loop iteration so the redrawing can take place. 这个想法很简单,您在每次循环迭代时都会中断执行,因此可以进行重绘。

EDIT: made a little improvement which prevents that the drawing results into a bottleneck. 编辑:进行了一些改进,以防止绘图成为瓶颈。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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