11

The title of the question expresses what I think is the ultimate question behind my particular case.

My case: Inside a click handler, I want to make an image visible (a 'loading' animation) right before a busy function starts. Then I want to make it invisible again after the function has completed. Instead of what I expected I realize that the image never becomes visible. I guess that this is due to the browser waiting for the handler to end, before it can do any redrawing (I am sure there are good performance reasons for that).

The code (also in this fiddle: http://jsfiddle.net/JLmh4/2/)

html:

<img id="kitty" src="http://placekitten.com/50/50" style="display:none">
<div><a href="#" id="enlace">click to see the cat</a> </div>

js:

$(document).ready(function(){
    $('#enlace').click(function(){
        var kitty = $('#kitty');
        kitty.css('display','block');

        // see: http://unixpapa.com/js/sleep.html
        function sleepStupidly(usec)
        {
            var endtime= new Date().getTime() + usec;
            while (new Date().getTime() < endtime)
                ;
        }

        // simulates bussy proccess, calling some function...

        sleepStupidly(4000);

        // when this triggers the img style do refresh!
        // but not before
        alert('now you do see it');

        kitty.css('display','none');
    });
});

I have added the alert call right after the sleepStupidly function to show that in that moment of rest, the browser does redraw, but not before. I innocently expected it to redraw right after setting the 'display' to 'block';

For the record, I have also tried appending html tags, or swapping css classes, instead of the image showing and hiding in this code. Same result.

After all my research I think that what I would need is the ability to force the browser to redraw and stop every other thing until then.

Is it possible? Is it possible in a crossbrowser way? Some plugin I wasn't able to find maybe...?

I thought that maybe something like 'jquery css callback' (as in this question: In JQuery, Is it possible to get callback function after setting new css rule?) would do the trick ... but that doesn't exist.

I have also tried to separte the showing, function call and hiding in different handlers for the same event ... but nothing. Also adding a setTimeout to delay the execution of the function (as recommended here: Force DOM refresh in JavaScript).

Thanks and I hope it also helps others.

javier

EDIT (after setting my preferred answer):

Just to further explain why I selected the window.setTimeout strategy. In my real use case I have realized that in order to give the browser time enough to redraw the page, I had to give it about 1000 milliseconds (much more than the 50 for the fiddle example). This I believe is due to a deeper DOM tree (in fact, unnecessarily deep). The setTimeout let approach lets you do that.

Community
  • 1
  • 1
javigzz
  • 942
  • 2
  • 11
  • 20
  • 1
    Didn't `window.setTimeout()` help? – Marat Tanalin Jun 01 '13 at 18:10
  • It should not be needed to use longer timeout delay. What you need is probably just to preload your image to be able to show it immediately. – Marat Tanalin Jun 03 '13 at 00:31
  • I see. But I understand that would be the case if the image was not loaded from the start by the browser (what would be reasonable since it is `display:none`, I guess). That's not the case, at least with chrome who does load the image from the very begining (seen with the developer tools). I still think is due to the legacy markup I am working with (a lot of embedded tables, really ... not dependent on me!). Actually firefox does it better (needs 'less time')... does it make sense? thanks again. regards – javigzz Jun 03 '13 at 11:11

5 Answers5

8

Use JQuery show and hide callbacks (or other way to display something like fadeIn/fadeOut).

http://jsfiddle.net/JLmh4/3/

$(document).ready(function () {
    $('#enlace').click(function () {
        var kitty = $('#kitty');


        // see: http://unixpapa.com/js/sleep.html
        function sleepStupidly(usec) {
            var endtime = new Date().getTime() + usec;
            while (new Date().getTime() < endtime);
        }

        kitty.show(function () {

            // simulates bussy proccess, calling some function...
            sleepStupidly(4000);

            // when this triggers the img style do refresh!
            // but not before
            alert('now you do see it');

            kitty.hide();
        });
    });
});
sdespont
  • 13,915
  • 9
  • 56
  • 97
  • Thanks for your answer which also works for the case I have suggested. That's why I also upvoted you (hope is not against the rules!). I selected the answer by Marat because it suited me better for my 'real case' which is a bit more complicated. Hence I think is more general. regards – javigzz Jun 01 '13 at 20:45
5

Use window.setTimeout() with some short unnoticeable delay to run slow function:

$(document).ready(function() {
    $('#enlace').click(function() {
        showImage();

        window.setTimeout(function() {
            sleepStupidly(4000);
            alert('now you do see it');
            hideImage();
        }, 50);
    });
});

Live demo

Marat Tanalin
  • 13,927
  • 1
  • 36
  • 52
4

To force redraw, you can use offsetHeight or getComputedStyle().

var foo = window.getComputedStyle(el, null);

or

var bar = el.offsetHeight;

"el" being a DOM element

ronnykaram
  • 81
  • 6
0

I do not know if this works in your case (as I have not tested it), but when manipulating CSS with JavaScript/jQuery it is sometimes necessary to force redrawing of a specific element to make changes take effect.

This is done by simply requesting a CSS property.

In your case, I would try putting a kitty.position().left; before the function call prior to messing with setTimeout.

Michael Trojanek
  • 1,813
  • 17
  • 15
0

What worked for me is setting the following:

$(element).css('display','none'); 

After that you can do whatever you want, and eventually you want to do:

$(element).css('display','block');
g00glen00b
  • 41,995
  • 13
  • 95
  • 133