How to delay showing of progress if ajax takes les

2020-02-29 04:59发布

问题:

I've got a few AJAX calls on a page. Some complete instantaneously, others take a few moments -- all depending upon what is clicked.

I want to add a "loader" that will display after X seconds while AJAX is working on results.

I've got a loader working:

   $(document).ajaxStart(function() {
        $("#loader").css("display","block");
    }).ajaxSuccess(function() {
        $("#loader").css("display","none");
    });

This functions.

However, it flashes on screen when the AJAX request is fast... like in the blink of an eye. Other than the "blinking" which occurs, it works great. So, I'm trying to delay the loader css change for a few seconds so that rapid results don't result in the "blink".

I've attempted to use setTimeout and the jQuery queue to delay things.. a la:

$(document).ajaxStart(function() {

    $("#loader").queue(function(){
        $(this).delay(5000);
        $(this).css("display","block");
        $(this).dequeue();
    });

}).ajaxSuccess(function() {
    $("#loader").css("display","none");
});

or:

$(document).ajaxStart(function() {

    setTimeout(function() { 
        $("#loader").css("display","block");
    }, 5000);

}).ajaxSuccess(function() {
    $("#loader").css("display","none");
});

(delaying jquery css changes)

or:

$(document).ajaxStart(function() {
    $("#loader").delay(5000).css("display","block")
}).ajaxSuccess(function() {
    $("#loader").css("display","none");
});

But the problem I'm running into is that any attempt to delay the css change on ajax start most often results in a delay... then the load appears (after ajax is done).

So the page loads the AJAX data then 5 seconds later... the loader appears.

Is there a good way to tell the ajaxstart() function to wait X seconds before executing?

  • I don't necessarily want to make this part of the actual ajax calls using something like the onBefore function(), primarily because some result are returned very quickly and don't need any progress indicator. More often than not.. the progress should not be shown. Most ajax requests are completed in under 5 seconds, there are only a handful that may take 10-20 seconds.

  • I have added the removal of the 'loader' to the complete function() in the ajax calls. Just to be certain the loader goes away when ajax is done. But this also fails if ajax is complete before any setTimeout() value is reached (and then the loader appears afterwards when it shouldn't).

I just want a css change on an element if the ajax is taking X seconds or more... can that be done?

Is there a way to time something within AJAX requests?

回答1:

setTimeout() has this nice feature where you can get a reference to the timeout and cancel it.

var ajaxLoadTimeout;
$(document).ajaxStart(function() {
    ajaxLoadTimeout = setTimeout(function() { 
        $("#loader").css("display","block");
    }, 5000);

}).ajaxSuccess(function() {
    clearTimeout(ajaxLoadTimeout);
    $("#loader").css("display","none");
});

This prevents the timeout from ever firing, rather than waiting for it and doing nothing if complete has been called (as in Jacob's answer).



回答2:

Just check if the ajax has completed before showing the #loader:

var ajaxDone; //create a global variable called ajaxDone
$(document).ajaxStart(function() {
    ajaxDone = false; //by default, set the ajax as not completed each time the ajax request is sent
    setTimeout(function() { 
        if(!ajaxDone) $("#loader").css("display","block");//checks if the ajax has finished yet before displaying #loader
    }, 5000);

}).ajaxSuccess(function() {
    ajaxDone=true;//When the ajax request finishes, it sets ajaxDone to true
    $("#loader").css("display","none");
});


回答3:

Thnaks to the great answers I was able to find a solution.

I ultimately wanted a localized "loading" image to show based on an element ID. The global ajaxStart() and ajaxComplete() functions don't handle local events. So I switched to the beforeSend() function with a timeout:

 $('.item').click( function (e) {
        e.preventDefault(); 
        var theID = $(this).attr('data');
        var theInfo = $('.holder#h' + theID);
        var loader = $('.waiting#w' + theID);
        $('.holder').slideUp(); //closes any open data holders
        var ajaxLoadTimeout;

        if (!$(theInfo).hasClass('opened')) {
            $(this).addClass('clicked');
            $(theInfo).addClass('opened');
            $(theInfo).html(''); //removes any existing data

        $.ajax({                                      
            url: '_core/inc/getdata.php',  
            type: 'POST',
            data: ({dataid: theID}),   
            dataType: 'html',

           //shows LOCAL loader before ajax is sent
           //but waits 3 milliseconds before doing so
          //most of the ajax calls take less than 3ms
          //without the timeOut the loader "flashes" for a milisecond
            beforeSend : function() {
                ajaxLoadTimeout = setTimeout(function() { 
                $(loader).show();
                }, 300);

            },
            success: function(data) {
                $(theInfo).html(data);

                //Hides LOCAL loader upon ajax success
                clearTimeout(ajaxLoadTimeout);
                $(loader).hide();
            },
            complete: function(){
                    $(theinfo).slideDown();
                }
              });

              } else {
                $(this).removeClass('clicked');
                $(theInfo).removeClass('opened').slideUp();
            }
      });

And the bit of relevant PHP/HTML:

echo '
<h1 class="item" data="'.$this_id.'">'.$this_title.' <span class="waiting" id="w'.$this_id.'"><i class="fa fa-refresh fa-spin fa-lg"></i></span></h1>

<section class="holder" id="h'.$this_id.'"></section>';

CSS: .waiting { discplay: none; }

I don't know that this is right or wrong, but it seems to be working as expected here.

It allows the font-awesome icon to appear next to the title of an item if that item takes more than a couple milliseconds to load.