1

What I am trying to achieve:

  1. user clicks on an element
  2. the screen shows the "calculation in progress" screen
  3. the system performs time-consuming math calculations
  4. the screen shows the result ("done")

Here's the stripped code:

<div id ="di" onclick="calc()">initial</div>
<script>

function calc()
{
var n,a=0;
document.getElementById('di').textContent="calculation in progress";
for(n=0;n<1000000000;n++) // Here's the time consuming calculation
    {
    a=a+n; // for clarity's sake, I removed a complicated math formula here
    }
document.getElementById('di').textContent="done "+a;
}
</script>

When I run it and click on the div, it takes a while and then changes the text to "done", so the user does not see the "calculation in progress" message at all - this is my problem.

To force a screen repaint to display the message before the calculations start, other threads suggest modifying CSS, hiding and immediately unhiding the element or using setTimeout, but nothing worked.

This will be a program that draws complicated math objects (fractals) and I will use canvas instead of a div, but I simplified the example above. Because of the future graphic interface, using "alert()" is not an option - the "calculation in progress" screen should turn to "done" immediately upon completion of the calculations.

Fractal
  • 13
  • 1
  • 3

3 Answers3

3

Because modern browsers may delay redrawing for better frame rate, versions with setTimeout may not work with too low time-outs.

If possible you need to use requestAnimationFrame. If its not posible then @Bálint answer should work, but with much bigger timeout (in my tests in Firefox its began work with timeout near 20-30). Actual timeout value is browser dependent (and probably system dependent too)

function very_long_func(){
   el= document.getElementById('di');

   requestAnimationFrame( function(){
      //edit dom for new frame;
      el.textContent = 'calculation in progress' 
      //browser will wait for this functions end, before start redraw.
      //So actual calucation must run outside of it
      setTimeout( function_with_actual_calculation, 1 ); 

   })
}
function function_with_actual_calculation(){
     //..your math here + updating textContent to "done" in the end.
} 
Arnial
  • 1,433
  • 1
  • 11
  • 10
2

IMO an easy way to handle this is to have your computation performed in "small" chunks by a timer function, for example:

function calcFractal(x0, y0, x1, y1) {
    ... compute the fractal for the tile (x0, y0)-(x1, y1) ...
}

var x = 0, y = 0;

function nextTile() {
    calcFractal(x, y, x+tw, y+th);
    x += tw;
    if (x >= width) {
        x = 0;
        y += th;
    }
    if (y < height) setTimeout(nextTile, 0);
}

nextTile();

This allows you to show progress (including for example a low resolution of the fractal, the percentage of the computation) and to allow interruption (with onclick events on a stop button for example).

If the tiles are not tiny the overhead will be acceptable, still maintaining the page reasonably responsive to both repaints and user interaction.

6502
  • 112,025
  • 15
  • 165
  • 265
1

You need to either wait a millisecond or do the calculations with a Worker.

The first example is probably the easiest, instead of calling calc directly, create a new function

function caller() {
     // insert "calculation in progress" in the body
    setTimeout(calc, 1);
}

Then call caller.

Bálint
  • 4,009
  • 2
  • 16
  • 27
  • You would also need to set the di div text before the `setTimeout` call so it actually updates – Zack Jun 21 '16 at 20:31
  • is this what you meant? it still doesn't work:
    initial
    – Fractal Jun 21 '16 at 20:46
  • @Zack same as above – Bálint Jun 21 '16 at 21:16
  • @Bálint - I did read them and I pasted the updated code. Can you confirm if I did it correctly? It's still behaving the same way as before your suggestion. – Fractal Jun 21 '16 at 21:26
  • @Fractal Insert the "calculation in progress" text **inside** the caller function **before** the setTimeout. – Bálint Jun 21 '16 at 21:29