I\'m dynamically updating a few elements after a setTimeout()
function. The jQuery function .text()
seems to dynamically update with each change of ind
By way of explanation, a lot of answers are going to point out the fact that most browsers run JavaScript on the UI thread, so they cannot update the interface when JavaScript is being executed. Instead, the browser will wait for the Javascript to finish and return control to the UI thread.
While this is important to keep in mind, it doesn't come into play for your code. If you were updating the progress bar doing something like this:
for (i = 0; i < len; i++) {
updateProgressBar(i)
};
Then it would definitely prevent the UI from updating until the end.
But you're already doing the right thing by using setTimeout or setInterval, which have asynchronous callbacks into the code. Meaning that the JavaScript is able to pause for long enough to pipe out any UI messages. As evidenced by the fact that the text is able to update dynamically.
As your question asks, why, if the UI is getting updated, is the progress bar not updated as well?
The answer lies in the fact that Bootstrap applies the following CSS to the progress bar:
.progress-bar {
-webkit-transition: width .6s ease;
-o-transition: width .6s ease;
transition: width .6s ease;
}
When the delay between calls is 0ms, the browser does have time to update the UI, but does not have time to complete the 600ms CSS transition. Consequently, you don't see the animation until the end.
As OhAuth points out, you can prevent the browser from delaying the width update by removing the transition effect with CSS like this:
.progress .progress-bar {
-webkit-transition: none;
-o-transition: none;
transition: none;
}
If you're looping through a lot of items, the incremental updates will provide some sort of animation. If you wanted to leave on the styling for a generic use case, you could toggle the transitions off and on in your code. As of jQuery 1.8 you can update CSS properties without the vendor prefix, so you'd just need to call .css("transition","none")
.
Of course, depending on how much work you're doing for each movie, the JavaScript may be able to finish before you can visually detect all of the UI changes, in which case extending the delay wouldn't hurt. If you'd like to test out longer running processes, you can use the following sleep function:
function sleep(sleepyTime) {
var start = +new Date;
while (+new Date - start < sleepyTime){}
}
Here's a demo you can play around where each setting is a variable that you can configure to see how it would react, but the best bet is just to conditionally remove transitions.
var delay, numMovies, throttledMovies, sleepTime, removeTransition;
$("#delay").change(function(){ delay = this.value }).change()
$("#movies").change(function(){ numMovies = this.value }).change()
$("#sleep").change(function(){ sleepTime = this.value }).change()
$("#transition").change(function(){ removeTransition = this.checked }).change()
$("#loadMovies").click(loadMovies);
function loadMovies() {
var i = 0;
throttledMovies = Movies.slice(0, numMovies)
if (removeTransition) {
$('#progress-bar-movie').css("transition","none");
}
var interval = setInterval(function () {
loadMovie(i)
if (++i >= numMovies) {
clearInterval(interval);
$('#progress-bar-movie').css("transition","width .6s ease");
}
}, delay);
};
function loadMovie(i) {
var movie = throttledMovies[i];
var percentComplete = ((i + 1) / numMovies) * 100;
sleep(sleepTime);
// update text
$("#procNum").text("(" + (i + 1) + "/" + numMovies + ")");
$("#procMovie").text(movie.Title);
// update progress bar
$('#progress-bar-movie')
.css('width', percentComplete+'%')
.attr('aria-valuenow', percentComplete);
};
function sleep(sleepyTime) {
var start = +new Date;
while (+new Date - start < sleepyTime){}
}
Current Title:
Progress: