0

I am developing a web page which uses <canvas> elements and javascript to draw in them (visualising data, so I can't readily set up a jsfiddle). The user can interact with the plot, but due to the size of the datasets, sometimes the canvas can take 1-2 sec to redraw. So I want to give the user some visual clue that things are happening. In principle, I thought this was easy, I have something like this:

$('#myCanvas').css('opacity',0.5);
$('#myWorkingAnimatedGif').css('display','block');
updateMyCanvas();

The updateMyCanvas() call look something like this

function updateMyCanvas()
{
   // loop through data, drawing via paths
   // takes 1-2 sec to run on big datasets
   // no asynchronous calls, just for loops
   $('#myCanvas').css('opacity',1);
   $('#myWorkingAnimatedGif').css('display','none');
}

i.e. plot the data, then remove the "I am working" visual clues.

However, this is not working; what happens instead is that the user clicks to replot, nothing happens for 1-2 sec, and then the data are replotted. i.e. the css commands are not implemented by the browser until after it's drawn the canvas, at which point it hits the css commands to undo those changes. In fact, if I edit updateMyCanvas() so that it does not restore the CSS to what it was, then when the user clicks, nothing happens for 1-2 sec, then the data replot and roughly simulataneously, the opacity changes and the animated gif appears.

On the other hand, if I instead do the following:

$('#myCanvas').css('opacity',0.5);
$('#myWorkingAnimatedGif').css('display','block');
window.setTimeout(function(){updateMyCanvas();}, 10);

It works fine, even though the 10ms timeout is around 1% of the time it takes updateMyCanvas() to run.

Does anyone know why this is? It's as if the javascript engine simply doesn't process my jquery css operations until after it's done all the canvas drawing stuff. I've tried this on firefox and Chrome (both under Linux) so it's not related to one specific engine.

Phil Evans
  • 790
  • 9
  • 18
  • 1
    [Related](http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful), and you can probably drop your 10ms timeout to 0. – James Thorpe Dec 09 '15 at 09:53
  • Sidenote: Why don't you use jquery's `.show()` / `.hide()` instead of `.css('display', ..)`? – hindmost Dec 09 '15 at 09:56
  • Using a 0 timeout on chrome is fine - on Firefox I get the same behaviour with out using timeout (i.e. the working clues never appear). I use css instead of (show/hide) because the latter animate (including opacity changes, which I don't want). If I do use show/hide and call `updateMyCanvas()` as a callback to that function, it works, but that's basically an equivalent of what I'm already doing as a workaround. – Phil Evans Dec 09 '15 at 09:59
  • 1
    You said it yourself : "no asynchronous calls, just for loops". Your browser doesn't have time to do the DOM paintings. Ps: if you've got the ability to split your drawing function in multiple timed-out functions, it may even be better. – Kaiido Dec 09 '15 at 10:00
  • @Kaiido - but if the for loops are taking 1-2 sec to run, why is there not time? Unless the browser is buffering the DOM calls, what I expected was for it first to apply the DOM calls, then do the for loops (taking 1-2 sec), and then do the DOM calls which undo the first ones. For cases with small datasets (so the for loops are very fast) I would not expect to see the DOM changes, but since the for loops take a while, I do expect to see them. That's what's puzzling me. – Phil Evans Dec 09 '15 at 10:02
  • 2
    0 timeout doesn't work in Firefox because [CSS animations are blocked by JS processing](http://stackoverflow.com/questions/15368087/why-are-css-animations-and-transitions-blocked-by-javascript) (but Chrome/Android process them differently, so that's why Chrome continues to work). Browser will see the DOM changes need to be repainted, and queue up a task to do so. It won't run the repaint task until the current task (running all the JS, including the canvas redrawing) has completed. – James Thorpe Dec 09 '15 at 10:05
  • @kaiido/ @James Thorpe - ok thanks that would explain it. So when I add the timeout, I guess the browser decides to run its DOM changes while waiting for the timeout to run. This presumably means that any behaviour like this (i.e. manipulating the DOM to show a placeholder while javascript works) is always going to be browser-dependent in terms of the whether it works, and the workaround (using timeouts) is not guaranteed even for future versions of the current browser? – Phil Evans Dec 09 '15 at 10:07
  • Showing a placeholder will always work, regardless of browser, so long as you `setTimeout(fn, 0)` the long work. What _may not_ work is CSS animations, which is what you're using here. If it was just a straight animated gif or similar that you made appear "in one hit", it would work fine. – James Thorpe Dec 09 '15 at 10:09
  • @JamesThorpe - I am not using CSS animations here though (am I?) I'm deliberately using the jquery .css() function to simply change the CSS without any animation. – Phil Evans Dec 09 '15 at 10:13
  • Sorry, I misread an earlier comment - looked like you were animating the CSS DOM changes. In that case, I don't know...! – James Thorpe Dec 09 '15 at 10:14
  • 1
    If you do something like `document.body.innerHTML=''; while(true){}`, the repaint will never occur. But, with `document.body.innerHTML=''; setTimeout(function(){while(true){},0);` the blocking script is placed at the end of the stack and will just occur when the browser would have finished all other ops it has to perform. – Kaiido Dec 09 '15 at 10:16

0 Answers0