DOM刷新长时间运行的功能

Mir*_*ror 24 javascript firefox internet-explorer dom

我有一个按钮,当它被点击时会运行一个长时间运行的功能.现在,当函数运行时,我想更改按钮文本,但我在Firefox,IE等浏览器中遇到问题.

HTML:

<button id="mybutt" class="buttonEnabled" onclick="longrunningfunction();"><span id="myspan">do some work</span></button>
Run Code Online (Sandbox Code Playgroud)

JavaScript的:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";

    //long running task here

    document.getElementById("myspan").innerHTML = "done";
}
Run Code Online (Sandbox Code Playgroud)

现在这在firefox和IE中都有问题,(在chrome中它可以正常工作)

所以我想把它放到一个settimeout:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";

    setTimeout(function() {
        //long running task here
        document.getElementById("myspan").innerHTML = "done";
    }, 0);
}
Run Code Online (Sandbox Code Playgroud)

但这对firefox也不起作用!按钮被禁用,更改颜色(由于新css的应用)但文本不会更改.

我必须将时间设置为50毫秒而不是仅仅0毫秒,以使其工作(更改按钮文本).现在我至少发现这个愚蠢.我可以理解它是否只能在0ms的延迟下工作,但在较慢的计算机中会发生什么?也许firefox在settimeout需要100ms?听起来很愚蠢.我尝试了很多次,1毫秒,10毫秒,20毫秒......不,它不会刷新它.只有50ms.

所以我遵循了这个主题的建议:

在javascript dom操作之后强制在Internet Explorer中进行DOM刷新

所以我试过了:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    var a = document.getElementById("mybutt").offsetTop; //force refresh

    //long running task here

    document.getElementById("myspan").innerHTML = "done";
}
Run Code Online (Sandbox Code Playgroud)

但它不起作用(FIREFOX 21).然后我试过:

function longrunningfunction() {
    document.getElementById("myspan").innerHTML = "doing some work";
    document.getElementById("mybutt").disabled = true;
    document.getElementById("mybutt").className = "buttonDisabled";
    var a = document.getElementById("mybutt").offsetTop; //force refresh
    var b = document.getElementById("myspan").offsetTop; //force refresh
    var c = document.getElementById("mybutt").clientHeight; //force refresh
    var d = document.getElementById("myspan").clientHeight; //force refresh

    setTimeout(function() {
        //long running task here
        document.getElementById("myspan").innerHTML = "done";
    }, 0);
}
Run Code Online (Sandbox Code Playgroud)

我甚至尝试过clientHeight而不是offsetTop但没有.DOM没有刷新.

有人可以提供一个可靠的解决方案,而不是非hacky?

提前致谢!

正如我在这里所建议的那样

$('#parentOfElementToBeRedrawn').hide().show();
Run Code Online (Sandbox Code Playgroud)

无济于事

在Chrome/Mac上强制DOM重绘/刷新

TL; DR:

寻找一个可靠的跨浏览器方法来强制DOM刷新而不使用setTimeout(根据长时间运行代码的类型,浏览器,计算机速度和setTimeout所需的不同时间间隔的首选解决方案需要50到100毫秒关于情况)

jsfiddle:http://jsfiddle.net/WsmUh/5/

Mik*_*ans 18

网页基于单个线程控制器进行更新,一半的浏览器在JS执行停止之前不会更新DOM或样式,从而将计算控制权交还给浏览器.这意味着如果你设置一些element.style.[...] = ...,它将不会启动,直到你的代码完成运行(完全,或因为浏览器看到你正在做一些让它拦截处理几毫秒的东西).

你有两个问题:1)你的按钮里面有一个<span>.删除它,只需在按钮上设置.innerHTML即可.但这当然不是真正的问题.2)你正在进行很长时间的操作,你应该非常认真地思考为什么,并在回答了原因之后,如何:

如果您正在运行一个很长的计算工作,请将其切换为超时回调.您的示例没有显示您的"长工作"实际上是什么(旋转循环不计算)但您有几个选项,具体取决于您使用的浏览器,只有一个GIANT书签:不要在JavaScript中运行长时间的作业,期间.JavaScript是按规范的单线程环境,因此您要执行的任何操作都应该能够在几毫秒内完成.如果它不能,那么你确实做错了什么.

如果你需要计算困难的东西,可以通过AJAX操作将其卸载到服务器(通用浏览器,通常会给你一个),以便更快地处理该操作; b)30秒的时间,你可以异步不等待结果要返回)或使用webworker后台线程(非常不通用).

如果你的计算需要很长时间但不是荒谬的,那么重构你的代码,以便执行具有超时喘息空间的部件:

function doLongCalculation(callbackFunction) {
  var partialResult = {};
  // part of the work, filling partialResult
  setTimeout(function(){ doSecondBit(partialResult, callbackFunction); }, 10);
}

function doSecondBit(partialResult, callbackFunction) {
  // more 'part of the work', filling partialResult
  setTimeout(function(){ finishUp(partialResult, callbackFunction); }, 10);
}

function finishUp(partialResult, callbackFunction) {
  var result;
  // do last bits, forming final result
  callbackFunction(result);
}
Run Code Online (Sandbox Code Playgroud)

长计算几乎总是可以重构为几个步骤,要么是因为您执行了几个步骤,要么是因为您运行了相同的计算一百万次,并且可以将其切割成批次.如果你有(夸大)这个:

var resuls = [];
for(var i=0; i<1000000; i++) {
  // computation is performed here
  if(...) results.push(...);
}
Run Code Online (Sandbox Code Playgroud)

那么你可以通过回调将其简化为具有超时放宽功能

function runBatch(start, end, terminal, results, callback) {
  var i;
  for(var i=start; i<end; i++) {
    // computation is performed here
    if(...) results.push(...);      } 
  if(i>=terminal) {
    callback(results);
  } else {
    var inc = end-start;
    setTimeout(function() {
      runBatch(start+inc, end+inc, terminal, results, callback);
    },10);
  }
}

function dealWithResults(results) {
  ...
}

function doLongComputation() {
  runBatch(0,1000,1000000,[],dealWithResults);
}
Run Code Online (Sandbox Code Playgroud)

TL; DR:不运行长计算,但如果必须,请让服务器为您完成工作并只使用异步AJAX调用.服务器可以更快地完成工作,并且您的页面不会阻止.

如何在客户端处理JS中的长计算的JS示例只是解释如果您没有选择执行AJAX调用时如何处理此问题,99.99%的时间不会是案件.

编辑

另请注意,您的赏金描述是XY问题的经典案例


Kyl*_*lie 7

解决了!! 没有setTimeout()!!!

在Chrome 27.0.1453,Firefox 21.0,Internet 9.0.8112中测试过

$("#btn").on("mousedown",function(){
$('#btn').html('working');}).on('mouseup', longFunc);

function longFunc(){
  //Do your long running work here...
   for (i = 1; i<1003332300; i++) {}
  //And on finish....  
   $('#btn').html('done');
}
Run Code Online (Sandbox Code Playgroud)

在这里演示!

  • 这打破了标准浏览器行为.在mousedown和mouseup为同一元素触发之后,才会触发单击事件.如果在鼠标位于元素之外时发生任何一种情况,则不会触发单击事件.有问题的用例:1)在元素内按下鼠标按钮,将鼠标移到外面,然后松开按钮.动画无限期运行,但处理永远不会开始.2)在元素外部按下鼠标按钮,将鼠标移动到内部,然后释放按钮.处理运行时没有可见的迹象表明它已经启动. (4认同)

Est*_*iaz 5

截至 2019 年,使用double requesAnimationFrame跳过一帧,而不是使用 setTimeout 创建竞争条件。

function doRun() {
    document.getElementById('app').innerHTML = 'Processing JS...';
    requestAnimationFrame(() =>
    requestAnimationFrame(function(){
         //blocks render
         confirm('Heavy load done') 
         document.getElementById('app').innerHTML = 'Processing JS... done';
    }))
}
doRun()
Run Code Online (Sandbox Code Playgroud)
<div id="app"></div>
Run Code Online (Sandbox Code Playgroud)


作为使用示例,请考虑在无限循环中使用蒙特卡罗计算 pi :

使用for循环来模拟while(true) - 因为这会破坏页面

function* piMonteCarlo(r = 5, yield_cycle = 10000){

  let total = 0, hits = 0, x=0, y=0, rsqrd = Math.pow(r, 2);
  
  while(true){
  
     total++;
     if(total === Number.MAX_SAFE_INTEGER){
      break;
     }
     x = Math.random() * r * 2 - r;
     y = Math.random() * r * 2 - r;
     
     (Math.pow(x,2) + Math.pow(y,2) < rsqrd) && hits++;
     
     if(total % yield_cycle === 0){
      yield 4 * hits / total
     }
  }
}

let pi_gen = piMonteCarlo(5, 1000), pi = 3;

for(let i = 0; i < 1000; i++){
// mocks
// while(true){
// basically last value will be rendered only
  pi = pi_gen.next().value
  console.log(pi)
  document.getElementById('app').innerHTML = "PI: " + pi
}
Run Code Online (Sandbox Code Playgroud)
<div id="app"></div>
Run Code Online (Sandbox Code Playgroud)

现在考虑requestAnimationFrame在两者之间使用更新;)

function* piMonteCarlo(r = 5, yield_cycle = 10000){

  let total = 0, hits = 0, x=0, y=0, rsqrd = Math.pow(r, 2);
  
  while(true){
  
     total++;
     if(total === Number.MAX_SAFE_INTEGER){
      break;
     }
     x = Math.random() * r * 2 - r;
     y = Math.random() * r * 2 - r;
     
     (Math.pow(x,2) + Math.pow(y,2) < rsqrd) && hits++;
     
     if(total % yield_cycle === 0){
      yield 4 * hits / total
     }
  }
}

let pi_gen = piMonteCarlo(5, 1000), pi = 3;


function rAFLoop(calculate){

  return new Promise(resolve => {
    requestAnimationFrame( () => {
    requestAnimationFrame(() => {
      typeof calculate === "function" && calculate()
      resolve()
    })
    })
  })
}
let stopped = false
async function piDOM(){
  while(stopped==false){
    await rAFLoop(() => {
      pi = pi_gen.next().value
      console.log(pi)
      document.getElementById('app').innerHTML = "PI: " + pi
    })
  }
}
function stop(){
  stopped = true;
}
function start(){
  if(stopped){
    stopped = false
    piDOM()
  }
}
piDOM()
Run Code Online (Sandbox Code Playgroud)
<div id="app"></div>
<button onclick="stop()">Stop</button>
<button onclick="start()">start</button>
Run Code Online (Sandbox Code Playgroud)