7

I have some processing that is going to take a few seconds so I want to add a visual indicator while it is in progress.

.processing
{
  background-color: #ff0000;
}

<div id="mydiv">
  Processing
</div>

Script:

$("#mydiv").addClass("processing");
// Do some long running processing
$("#mydiv").removeClass("processing");

I naively thought that the class would be applied to the div and the UI would be updated. However, running this in the browser (in Firefox at least) the div is never highlighted. Can someone explain to me why my my div never gets highlighted? The class is added, the processing takes place and then the class is removed; the UI is not updated in between and the user never sees the red background.

I know JS is single-threaded but I'd always presumed the browser rendering would run synchronously as and when the DOM is updated. Is my assumption incorrect?

And more importantly, what is the recommended way to achieve this effect? Do I have to result to using setTimeout to make the slow processing asynchronous and work with a callback? There must be a better way as I really don't have any need for async behaviour here; I just want the UI to refresh.

EDIT:

JSFiddle: http://jsfiddle.net/SE8wD/5/

(Note, you may need to tweak the number of loop iterations to give you a reasonable delay on your own PC)

8
  • 1
    I believe such changes will make it directly to the DOM, but the browser will wait until the JavaScript execution is "idle" before doing any repainting. Commented Mar 23, 2012 at 10:58
  • "Do some long running processing" has some asynchronous call? (ajax, settimeout etc) Commented Mar 23, 2012 at 11:00
  • I can't really see where your code goes wrong, because it seems fine of what you have posted. Maybe there is some !important in your external css file. Are you sure the class get's added ( have you inspected it via firebug) and does the process really take that long ? maybe it happens too fast Commented Mar 23, 2012 at 11:00
  • @chumkiu havin an ajax or settimout in his code will not work because the add and remove will not wait for the asynchronous to finish ! Commented Mar 23, 2012 at 11:01
  • @EvilP exactly. His assumptions is correct, so there is something of wrong somewhere else Commented Mar 23, 2012 at 11:05

4 Answers 4

2

You probably should put the processing out in a seperate event, like this;

$("#mydiv").addClass("processing");
setTimeout(function(){
   // This runs as a seperate event after
   // Do some long running processing
   $("#mydiv").removeClass("processing");
},1);

That way the browser should redraw the screen with the processing, while the long running step will kick off as a seperate event, and remove the processing message when done.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, this is what I suspected. I really hoped there was a way to avoid this force the browser to re-render after changes to the DOM.
warning - I find it starts working after 5ms timeout - anything shorter and chrome still waits.
@user2486570 -- I doubt it -- JS is single threaded event loop -- so it has only those race conditions you create yourself -- if you see a 5ms problem is is probably because you have something else running with a timer or a DOM ready event that is conflicting with your other code.
nope - browser doesn't trigger repaint unless the delay is long enough - I tried just now and actually, even 5 ms isn't enough sometimes.
my case is with prompt() instead of long running operation, but the result is the same.
1

I not recommend to freeze browser for a weight script. In this case I prefer to change the code for not freeze browser and DOM. If in your code there is some loop, you can call a function with a delay (using setInterval) and store some status variable somewhere. For example:

for(var i=0; i<1000000; i++) {
    // do something
}

Can be:

var i = 0;
var intervalID;
var _f = function() {
    // do something
    i++;
    if(i==1000000) clearInterval(intervalID);
}
intervalID  = setInterval(_f,1);

Or something of similar and more optimized. Your code will be a little bit more complex, and slower.. . but so you prevent browser freeze and you can make an advanced preload status bar.

4 Comments

Thanks, this was what I meant when I mentioned setTimeout. I realise I could do this but I was hoping to avoid the added complexity of async calls. That opens a whole class of other problems where state is indeterminate when the async callback occurs.
I know. You can see also web worker html5rocks.com/en/tutorials/workers/basics
Thanks for the link. Interesting stuff, but unfortunately won't help me as I need cross-browser support.
@njr the last suggest, is not a solution but a "stupid" trick, insert an alert('Please wait'); after addClass, that will force browser rendering :)
0

A browser is quite free about when to repaint and when to reflow. Different engines will show different behavior. For further reading, see When does reflow happen in a DOM environment?. In your example, the browser sees you adding a class and removing it directly after that, so he might not do anything visible.

To avoid that, you may apply a force-redraw-hack, or make your computation asynchronous with WebWorkers or setTimeout or something.

Comments

0

Force a redraw. Alternatively, use Soren's code because processing on the UI thread sounds bad...

6 Comments

However, jQuery plugins seem to be down at the moment. Added an alternative. It could be that you don't need to defer n.parentNode.removeChild(n); if so, feel free to edit my answer.
I tried this on my jsFiddle with no success. I'm obviously not using it properly. Can you add to Fiddle please?
Sorry, but the Fiddle doesn't work. The class is applied and then script aborts with a script error. The "Finished" message is never displayed.
Thanks for your help. I did mention the browser in the OP (it's Firefox, latest version). But is doesn't work for me in Chrome either. In both browsers the red highlighting is displayed and the script aborts. The "Finished" message never appears. I'm looking at this Fiddle jsfiddle.net/SE8wD/26 (this one really works for you?)
Thanks for all your efforts. This certainly works, although it is really just using hide() to provide the callback rather than setTimeout(). It's making an async call either way, which is what I really wanted to avoid. That async call will not fire until the rest of the inline code has been processed which then needs extra synchronisation.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.