问题
What I am trying to achieve:
- user clicks on an element
- the screen shows the "calculation in progress" screen
- the system performs time-consuming math calculations
- 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.
回答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
.
回答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.
回答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.
}
来源:https://stackoverflow.com/questions/37953871/how-to-force-repaint-in-js