【问题标题】:How to show CSS loader while syncronous JavaScript is running如何在同步 JavaScript 运行时显示 CSS 加载器
【发布时间】:2017-10-01 17:43:10
【问题描述】:

根据我自己的经验,并且与 this answer 一致,在 JavaScript 代码运行时不会更改 UI。

示例
当我单击“运行脚本”按钮时,我希望出现加载动画,然后我希望运行一些 JavaScript,当 JavaScript 完成运行时,我希望加载动画消失。我创建了a codepen here,它(可以预见地)失败了。最相关的代码部分是:

$('#run-script-btn').on('click', function() {
    startLoading();
    longLoadingScript(10000);
    stopLoading();
});

startLoading() 更改 CSS 以显示加载器,但它实际上不会影响 UI,直到 JS 完成运行,此时会调用 stopLoading() - 所以基本上我们看不到加载图标。

我想出的解决方法是在longLoadingScript()stopLoading() 代码周围放置一个setTimeout(),以便让浏览器有时间在startLoading() 调用中实际影响UI。工作代码在a codepen here,相关部分如下所示:

$('#run-script-btn').on('click', function() {
    startLoading();
    setTimeout(function() {
        longLoadingScript(10000);
        stopLoading();
    }, 100);
});

问题
这是一个好方法吗?是否有更好/更成熟的模式来处理这种情况?

【问题讨论】:

    标签: javascript html css browser css-animations


    【解决方案1】:

    这实际上发生在你的情况下,

    1) 浏览器根据从您的服务器接收到的 HTML 和 CSS 创建渲染树。然后它将渲染树绘制到您的窗口。

    2) 因此,当您在 DOM 中进行任何更改时,例如显示更改(块/无)。渲染树的部分(或完整)需要重新评估。这称为回流

    3) 浏览器缓存所有重排/重绘更改,并在主代码块执行完成后执行。

    4) 案例 1 - 没有 setTimeout:主代码块执行 + 重排/重绘所有更改。 (显示:阻止,然后无)。不会看到任何更改。

    案例 2 - 使用 setTimeout:主代码块执行 + 回流(显示:块)+ 事件循环将 setTimeout 回调推送到调用堆栈 - 回流将再次发生(显示:无)

    它也适用于 0 毫秒的 setTimeout。

    回答您的问题:这对我来说看起来不错,我认为使用它没有任何问题。

    $('#run-script-btn').on('click', function() {
        startLoading();
        setTimeout(function() {
            longLoadingScript(100);
            stopLoading();
        }, 0);
    });
    

    其他解决方案是使用 offsetHeight 重排元素。但是,它似乎不适用于我的 chrome+osx 平台。在 Firefox 中运行良好。

    $('#run-script-btn').on('click', function() {
            startLoading();
            var res = $('.page-loading').outerHeight(); // calculates offsetHeight and redraw the element
            longLoadingScript(100);
            stopLoading();
    });
    

    参考:http://frontendbabel.info/articles/webpage-rendering-101/

    【讨论】:

    • 朋友你错了,javascript是单线程的,所以运行像for..loop这样的高CPU消耗操作或同步代​​码会阻塞你网页中的所有内容,您将无法进行事件点击。
    【解决方案2】:

    这不是一个简短的答案,但我会尽量简短。

    首先,JavaScript 是单线程的,所以运行阻塞 CPU 代码 for...loopwhile...loop 之类的操作将阻止 你的网页。

    运行下面的片段,看看 stackoverflow 中的所有内容如何冻结 9 秒,您也会看到 setInterval 也会停止

    setInterval(()=>{
       console.log( Math.random() )
    }, 500)
    
    setTimeout(()=>{
      blocker(9000) //This stop your entire web page for 9 seconds
    }, 2500)
    
    function blocker (ms) {
        var now = new Date().getTime();
        while(true) {
            if (new Date().getTime() > now +ms)
                return;
        }   
    }
    如您所见,您无法采取任何措施来防止这种情况发生,这是因为某些网页非常缓慢或无缘无故地冻结。

    对于那个 ES6 有 WebWorkers

    一种允许您在后台运行代码的技术,它们的问题是它们使用静态.js 文件,您必须在执行它们之前编写这些文件。

    但是,我刚刚创建了一个抽象所有这些的类,并使用通用BlobFIles 来创建Generic Web Workers

    我的代码允许您在后台运行脚本并在已解决的 Promise 中获得它的返回。

    class GenericWebWorker {
        constructor(...ags) {
            this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
        }
    
        async exec(cb) {
            var wk_string = this.worker.toString();
            wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));            
            var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
            var wk = new Worker(wk_link);
    
            wk.postMessage({ callback: cb.toString(), args: this.args });
     
            var resultado = await new Promise((next, error) => {
                wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
                wk.onerror = e => error(e.message);
            })
    
            wk.terminate(); window.URL.revokeObjectURL(wk_link);
            return resultado
        }
    
        async parallel(array, cb) {
            var results = []
            for (var item of [...array])
                results.push( new GenericWebWorker(item, ...this.args).exec(cb) );
    
            var all = await Promise.all(results)
            return all
        }
    
        worker() {
            onmessage = async function (e) {
                try {                
                    var cb = new Function(`return ${e.data.callback}`)();
                    var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);
    
                    try {
                        var result = await cb.apply(this, args); //If it is a promise or async function
                        return postMessage(result)
    
                    } catch (e) { throw new Error(`CallbackError: ${e}`) }
                } catch (e) { postMessage({error: e.message}) }
            }
        }
    }
    
    function blocker (ms) {
        var now = new Date().getTime();
        while(true) {
            if (new Date().getTime() > now +ms)
                return;
        }   
    }
    
    setInterval(()=> console.log(Math.random()), 1000)
    
    setTimeout(()=> {
      var worker = new GenericWebWorker(blocker)
      console.log('\nstarting blocking code of 10 scds\n\n')
      worker.exec(blocker => {
          blocker(10000)
          return '\n\nEnd of blocking code\n\n\n'
      }).then(d => console.log(d))
    }, 4000)
    如果您想简要解释一下我的代码:
    /*First you create a new Instance and pass all 
      the data you'll use, even functions*/
    var worker = new GenericWebWorker(123, new Date(), fun1)
    
    //And then you use them and write your code like this
    worker.exec((num, date, function1) => {
        console.log(num, date)
        function1() //All of these run in backgrownd
        return 3*4
    
    }).then(d => console.log(d)) //Print 12
    

    【讨论】:

      【解决方案3】:

      阅读时使用promise 更容易理解:

      $('#run-script-btn').on('click', function() {
          startLoading();
          longLoadingScript(10000)
              .then(function () {
                  stopLoading();
              });
      });
      
      longLoadingScript(ticks) {
          var promise = new Promise(function (resolve, reject) {
              ajax({
                  success: function (value?) { promise.resolve(value); },
                  fail: function (reason) { promise.reject(reason); }
              })
          });
      
          return promise;
      }
      

      IE(非边缘)将需要一个 polyfill (possible polyfill)

      【讨论】:

      • 但这是否处理了在运行 JavaScript 时更改 CSS 动画的能力?如果你给出一个可行的例子,我会接受这个答案。
      猜你喜欢
      • 1970-01-01
      • 2017-06-11
      • 2011-10-16
      • 1970-01-01
      • 2014-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-10
      相关资源
      最近更新 更多