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

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

我有一个 JavaScript 方法可以迭代 ~200 个输入、插入值并触发它们的 onchange 事件。在 Chrome 中大约需要 2000 毫秒,在 IE 中大约需要 10000 毫秒。

我想制作一个进度条来显示进度。

现在,问题是浏览器不会重新绘制进度条,直到进程结束时,它会立即达到 100%。

在这里研究后,我找到了三种方法并尝试了所有方法,但它们都不起作用:

这是我的代码:

    <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:

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

如何强制浏览器重绘?

可能是您的进程太繁重,导致执行线程崩溃。这意味着在计算时浏览器会稍微冻结,并且在处理计算时可能无法与 UI 进行其他交互。

为了避免这种情况,HTML5 提供了网络工作者技术。这是一种在浏览器环境中模拟 多任务处理的方法,可让您执行繁重的任务,避免浏览器冻结。

这里有一篇关于这项技术的优秀介绍文章,我已经成功地使用它创建了一个能够上传 Gb 长度文件并保持实时进度条的文件上传器:

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

希望对您有所帮助。

好的,所以我之前的答案没有像我希望的那样工作,所以这是一个工作示例:

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

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

我认为这可能是最好的选择,因为它不涉及破解,这正是此类任务的方式。唯一的问题是很难在 worker 中获得 JQuery。最好的方法是在主线程中完全检索 SQL 数据集,然后传输到工作线程中。

@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>

这个想法很简单,你在每次循环迭代时中断执行,这样就可以进行重绘。

编辑:做了一点改进,防止绘图导致瓶颈。