Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animated image not moving during long Javascript method call

During a method that runs for 10 seconds to add some elements to the html, the animated gif don't move at all, giving the feel that the web page is stuck. Any way around this.

Example code:

    $('#button).click(function() {   
        showAnimatedGif();
        longRunningMethod();    
        hideAnimatedGif();
    });  

One way around this is to breakup the long running method in multiple steps and animating this way, but then you have to write your code this way for each long running method.

Wondering if there is any otherway to do this ?

like image 305
mrjohn Avatar asked Dec 14 '10 22:12

mrjohn


1 Answers

The only way to ensure that the animation actually occurs is for longRunningMethod to yield to the browser periodically. It's not uncommon for the UI rendering thread to block on the JavaScript thread, so changes you make to the DOM aren't shown until the next time the JavaScript thread yields. You can yield by calling setTimeout with a timeout of 0 (which will be longer than 0ms — obviously — on most browser implementations; many clamp that value to at least 4ms and possibly as much as 10ms).

Example: Here's a script that adds 500 divs, but gives the browser no opportunity to show the work in progress or (usually) animate anything:

jQuery(function($) {

  $("#btnGo").click(function() {
    var n;

    display("Loading...");

    for (n = 0; n < 500; ++n) {
      $("<div/>").html("Div #" + n).appendTo(document.body);
    }

    display("Done loading");

    function display(msg) {
      $("<p/>").html(msg).appendTo(document.body);
    }
  });

});​

Live example * Example with spinner graphic

As you can see, it never yields, and so when you click the button, the page freezes for a moment, and then all 500 divs and the messages appear.

Contrast with this other end of the spectrum, which yields between every div:

jQuery(function($) {

  $("#btnGo").click(function() {
    display("Loading...");

    addDivs(0, 500, function() {
      display("Done loading");
    });

    function addDivs(current, max, callback) {
      if (current < max) {
        $("<div/>").html("Div #" + current).appendTo(document.body);
        setTimeout(function() {
          addDivs(current + 1, max, callback);
        }, 0);
      }
      else {
        callback();
      }
    }

    function display(msg) {
      $("<p/>").html(msg).appendTo(document.body);
    }
  });

});​

Live example * Example with spinner graphic

Since that yields, you can see the work in progress.

You might notice that the second script takes longer, possibly much longer, in real time to achieve the same result. This is because the yields take time, as does the browser updating the display. In practice, you'll want to find a balance between the first example and the second — something that yields often enough to show progress, but not so often that the process takes an unnecessarily long time to complete.

like image 95
T.J. Crowder Avatar answered Sep 27 '22 20:09

T.J. Crowder