Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS transitions blocked by JavaScript

I am trying to create a loading bar during a very intensive period of JavaScript where some pretty heavy 3d arrays are built and filled. This loading bar needs to remain empty until the user clicks a button.

The freezing occurs whether or not I'm using -webkit-transition (This app can be chrome exclusive, cross browser is not necessary in my case).

Seeking simplicity I've built my bar like this...

<div id="loader">
    <div id="thumb">
    </div>
</div>

... and then sought to increment that bar at various stages of my main for loop:

for(i = 0; i < 5 ; i++){
    document.getElementById('thumb').style.width = i*25 + '%';
    //More Code
}

Problem is that everything freezes until the JavaScript finishes. I found a similar question on Stack Overflow, Using CSS animation while javascript computes, and in the comments found and considered and/or tried the following:

  • Web Workers

    Don't think it'll work since my script is filling an array with objects and constructors containing functions which according to this site isn't going to work

  • jQuery

    Not an option, I can't use external libraries in my app - in any case, importing a whole library just for a loading bar seems kind of like overkill...

  • Keyframes

    This was promising and I tried it, but in the end it freezes also, so no joy

  • timeOut()s

    Thought about this, but since the point of the loading bar is to reduce frustration, increasing the waiting time seems counter-productive

I'd be happy to have any incrementation of the bar at this stage, even if it's not smooth! I'm pretty sure this is a problem that has struck more than just me - maybe someone has an interesting solution?

P.S.: I'm posting this as a new question rather than adding to the referenced question since I'm specifically seeking help with JavaScript (not jQuery) and would prefer if I could get it using a transition (!=animation) on the width.

like image 635
agryson Avatar asked Nov 19 '12 20:11

agryson


2 Answers

Some people already mentioned that you should use timeouts. That's the appropriate approach, bc it'll give the browser time to "breathe" and render your progress bar mid-task.

You have to split your code up to work asynchronously. Say you currently have something like this:

function doAllTheWork() {
  for(var i = 0; i < reallyBigNumberOfIterations; i++) {
    processorIntensiveTask(i);
  }
}

Then you need to turn it into something like this:

var i = 0;
function doSomeWork() {
  var startTime = Date.now();
  while(i < reallyBigNumberOfIterations && (Date.now() - startTime) < 30) {
    processorIntensiveTask(i);
    i++;
  }

  if(i < reallyBigNumberOfIterations) {
    // Here you update the progress bar
    incrementBar(i / reallyBigNumberOfIterations);

    // Schedule a timeout to continue working on the heavy task
    setTimeout(doSomeWork, 50);
  }
  else {
    taskFinished();
  }
}

function incrementBar(fraction) {
  console.log(Math.round(fraction * 100) + ' percent done');
}

function taskFinished() { console.log('Done!'); }

doSomeWork();

Note the expression (Date.now() - startTime) < 30. That means the loop will get as much done as it can in the span of 30 milliseconds. You can make this number bigger, but anything over 100ms (essentially 10 frames-per-second) is going to start feeling sluggish from the user's point of view.

It may be true that the overall task is going to take somewhat longer using this approach as opposed to the synchronous version. However, from the user's experience, having an indication that something is happening is better than waiting indefinitely while nothing seems to be happening – even if the latter wait time is shorter.

like image 125
meetamit Avatar answered Sep 20 '22 17:09

meetamit


Have you tried going even simpler and making a function, let's say:

Pseudo:

Function increment_bar(amount = 10)
{
 document.getElementById('thumb').style.width = i*amount + '%';
}

Then from wherever you are doing your processing work just calling that function every x seconds or whenever you hit a certain point in processing (let's say 10-20% completion?)

Pseudo:

{
    Doing_work_here;
    increment_bar(25);
    LOOP
}
like image 32
Paul Avatar answered Sep 19 '22 17:09

Paul