3

I have a following function which slides a relatively positioned element 1000px off of where it is now.

for (var i = 0; i < 1000; i++) {
    $('.my-element').css(
        'left',
        parseInt($('.my-element').css('left'), 10) + 1
    );
}

This does not produces a sliding effect. Rather, by the end of the execution, the element moves abruptly 1000px to its right.

Now, if I wrap the UI updates in setTimeout like below:

for (var i = 0; i < 1000; i++) {
    setTimeout(function () {
        $('.my-element').css(
            'left',
            parseInt($('.my-element').css('left'), 10) + 1
        );
    }, 0);
}

This produces the visual effect of the element sliding 1000px to its right.

Now, according to my understaning and this SO thread, Why is setTimeout(fn, 0) sometimes useful?, UI updates are queued in the browser event queue in the same way async callbacks are queued like setTimeout callback.

So, in case first, basically, a queue of 1000 UI updates are made when the for loop is executed.

And in case second, first, a queue of 1000 setTimeout callbacks is created, which on execution, creates another queue of 1000 UI updates.

So, eventually, both cases create the same queue of 1000 UI updates. Then why the difference in the visual result?

I must be looking over some important JavaScipt and browser rendering concept here. Anyone who can enlighten me will be much appreciated.

Note: Above examples are purely for understanding purpose and not an attempt to create a JS function to slide a DOM element.

Community
  • 1
  • 1
Prashant
  • 7,340
  • 2
  • 25
  • 24
  • 1
    Logan definitely answers your question, but I would suggest looking into [jQuery.animate](http://api.jquery.com/animate/) to perform the sliding effect instead of looping. Just think - every iteration of that for loop is transversing the DOM looking for all elements with a class of `.my-element` - hardly efficient! – chazsolo Dec 05 '14 at 20:16
  • This example is purely for the purpose of understanding how browser executes JavaScript and updates UI. Not an attempt to create a sliding function. – Prashant Dec 05 '14 at 20:22

1 Answers1

6

This is probably the best way to think about it. The browser can do one of two things. Either it runs your javascript or it renders the webapge, it cannot do both.

This is because javascript code is 100% blocking, meaning it will never give up control until the browser has executed all blocking code.

You first example contains only blocking code so the browser is never given the opportunity to render until the element is already where it needs to be.

Your second example contains blocking code that uses setTimeout (delayed blocking code) which queues a bunch of blocking code to be executed later (after all other blocking code has completed) at the browsers discretion (between its rending and javascript running cycles).

So the second example the loop will completely execute, queuing 1000 functions to execute at some point in time but as close to 0ms as possible. Now that the blocking code has completed one or more setTimeout may execute or the browser may render, its pretty random what actually happens though. But it will weave back and forth between rendering and executing javascript.

Take this code for example.

setTimeout(function () { //this makes it so the page loads and sits for a second
    var delay = 100, //delay between animations
        distance = 25, //total distance moved
        sync = false; //should this use blocking code

    if (sync) {
        var i = 0,
            elapsed = 0,
            last = new Date();
        while (i < distance) {
            var now = new Date();
            elapsed += (now - last);
            last = now;
            if (elapsed >= delay) {
                move(i++);
                elapsed -= delay;
            }
        }
    } else {
        for (var i = 0; i < distance; i++) {
            assyncMove(i, delay * i);
        }
    }

    function assyncMove(position, delay) {
        setTimeout(function () {
            move(position);
        }, delay);
    }

    function move(position) {
        $("div").css("left", position);
    }
}, 1000);

You can change the delay, distance, and sync variables. Both loops wait to move the element delay milliseconds between each animation. They will both move a div a total of distance pixels. However one (setTimeout) will have a visible animation while the other will just shoot over. If you make the delay or distance too long for the sync method you will actually freeze the browser, the assync solution will not have that issue!

http://jsfiddle.net/j79s4o4w/3/

Logan Murphy
  • 6,120
  • 3
  • 24
  • 42
  • But if UI updates are queued to the browser event queue, then first example adds to the queue 1000 UI updates where the browser would have to paint the element in different position for each queue item. (therefore resulting in sliding visual effect, I think) In second example, 1000 queued setTimeout callbacks would have to create 1000 UI updates at the end of the setTimeout callbacks which to me is the same as the first example. – Prashant Dec 05 '14 at 20:20
  • @PrashantPalikhe There is no queue in the first example. The script changes the element's left CSS property 1000 times. When the browser gets to rendering the page it sees only the end result. Just changing the CSS property does not trigger a redraw by itself. – JJJ Dec 05 '14 at 20:26
  • http://stackoverflow.com/a/4575011/694877 OK, that means this comment on the linked thread is misleading. Because in his example without setTimeout, he mentions of the UI updates being added to the queue. And he even goes ahead to mention that by the end of JS function, the re-draw of the element happens so fast that even though three different UI updates occur, only the last one seems to be visible. – Prashant Dec 05 '14 at 20:33
  • so i added code that shows no matter what your delay is, you will always experience lag in rendering due to the execution of blocking javascript code. you can follow the jsfiddle link and mess with the variables as well. – Logan Murphy Dec 05 '14 at 21:10
  • Hi @LoganMurphy, you mentioned "Now that the blocking code has completed one or more setTimeout may execute or the browser may render, its pretty random what actually happens though.". Does that mean if I add a new DOM element before ```setTimeout```, and the callback will try using the DOM element, there's no guarantee that the new DOM is created before callback runs? – Junlong Wang Nov 15 '19 at 00:18
  • @junlongwang Dom node creation is synchronous (blocking), you can learn more about is concurrency model here https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop, it is not as random as I suggest it is – Logan Murphy Nov 15 '19 at 13:37