5

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 ?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
mrjohn
  • 1,141
  • 2
  • 13
  • 21

2 Answers2

6

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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • what is the exact call to setTimeout look like for this ? – mrjohn Dec 14 '10 at 23:14
  • @mrjohn: Added an example with and without. – T.J. Crowder Dec 14 '10 at 23:15
  • Interesting point about finding the right balance. I wonder if it's possible to execute to the browser's capacity before yielding to get the best result. Kind of like FPS in graphics. – David Tang Dec 14 '10 at 23:17
  • The problem is that instead of the text loading I have an animated gif and it does not move/animate. I tried with the setTimeout, but that did not help. – mrjohn Dec 14 '10 at 23:19
  • 1
    @box9: Yeah. You might measure how long the yield takes (time between the function ending and the next scheduled function starting), and then yield more or less often on the basis of that. – T.J. Crowder Dec 14 '10 at 23:19
  • @mrjohn: That's strange, it does for me: http://jsbin.com/oqasi4/3 vs. http://jsbin.com/oqasi4/4 The spinner spins during the loading on the latter but not on the former, on Chrome for Linux and (just to be extreme) IE6 on Windows. The degree to which the animation occurs will be directly tied to the frequency with which you yield back to the browser (on some browsers). – T.J. Crowder Dec 14 '10 at 23:25
  • @T.J. Good idea. I decided to paginate a 2000-row table because of rendering speed, but I might test this technique out and make something generic out of it if it works. Still won't go for the full 2000 rows though, as scrolling is still problematic. – David Tang Dec 14 '10 at 23:27
  • setTimeout(function () { document.getElementById("myimg").src = document.getElementById("myimg").src;}, 150); does not seem to do the spinning – mrjohn Dec 14 '10 at 23:31
  • If you increase your loop to say 20000 then it does not spin until the for loop is finished – mrjohn Dec 14 '10 at 23:38
  • @mrjohn: *"If you increase your loop to say 20000 then it does not spin until the for loop is finished"* Does for me, same browsers: http://jsbin.com/oqasi4/5 *"`setTimeout` ... does not seem to do the spinning"* Setting the image's `src` back to itself is basically a no-op, it's the `longRunningMethod` you need to break up into discrete parts and schedule via `setTimeout`, nothing to do with the image. – T.J. Crowder Dec 15 '10 at 09:16
0

If your long running method is doing an synchronous ajax call. Use the asynchronous option on the open method.

xmlHttp.open('POST', ajaxurl, true);

This will allow your spinning graphics to spin.

user3158212
  • 555
  • 6
  • 16