I\'m dynamically updating a few elements after a setTimeout()
function. The jQuery function .text()
seems to dynamically update with each change of ind
It is due to the way bootstrap animates changes in progress bar state. If the timeout interval is smaller than the animation time it will queue the redraw.
Try adding this to your progress bar's CSS:
-webkit-transition: none;
transition: none;
Check my fiddle
Text and Progress Bar change dynamically, but with delay === 0
it's very fast: (http://jsfiddle.net/sergdenisov/mh4o1wxz/1/):
HTML:
<div class="text">Text</div>
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0;">
</div>
</div>
Javascript:
var delay = 0;
var percent = 0;
var iID = setInterval(function() {
percent += 10;
$('.text').text('Text ' + percent + '/100');
$('.progress-bar').css('width', percent + '%').attr('aria-valuenow', percent);
if (percent === 100) {
clearInterval(iID);
}
}, delay);
Another work around (with reference to my previous answer):
If you must keep the animation, you could use a conservative interval delay of say 100 milliseconds. A delay which shouldn't be noticeable to the user.
If you really need the delay to be zero for the calculations then I would separate the calculations and the setting of the progress bar value so that you are calculating the value in one setInterval
, and then run a separate setInterval
that updates the bar every 100 ms to this value.
This way your calculations are up to date and your UI has time it needs to update as well.
If you want them both in the same method then I think you need to add a minimum 100 ms delay.
Example: https://jsfiddle.net/dk7g51g4/3/
Javascript:
var _ValueToSet = 0;
$(document).ready(function () {
// SET VALUE EXTREMELY FAST (your current code)
setInterval(function () {
_ValueToSet = Math.round(Math.random() * 100); // get value we are going to set
}
, 0); // run as fast as possible
// RUN UI UPDATE AT 100MS (separate thread for ui updates).
setInterval(function () {
setRandom()
}
, 100); // give ui 100ms to do its thing
});
function setRandom() {
var elem = $('.progress-bar'); // what is our target progress bar
// set both style width and aria-valuenow
elem.css('width', _ValueToSet + '%');
elem.attr('aria-valuenow', _ValueToSet);
}
HTML:
<div class="progress progress-striped active">
<div class="progress-bar"
style="width:0%"
role="progressbar"
aria-valuenow="0"
aria-valuemin="30"
aria-valuemax="100">
</div>
</div>
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){}
}
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.js"></script>
<script src="http://kylemitofsky.com/libraries/libraries/movies.js"></script>
<!-- params -->
<label for="delay">Delay (ms)</label>
<input type="number" value="0" min=0 max=1000 id="delay"/>
<label for="movies"># Movies </label>
<input type="number" value="250" min=0 max=250 id="movies"/>
<label for="sleep">Sleep time (ms)</label>
<input type="number" value="0" min=0 max=1000 id="sleep"/>
<label for="transition">Remove transition? </label>
<input type="checkbox" checked="true" id="transition"/><br/><br/>
<button class="btn btn-primary btn-lg" id="loadMovies">
Load Movies
</button><br/><br/>
<p>
<b>Current Title</b>: <span id="procMovie"></span><br/>
<b>Progress</b>: <span id="procNum"></span>
</p>
<div class="progress">
<div class="progress-bar" role="progressbar" id="progress-bar-movie"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
This is probably a redraw issue. Since the delay is set to 0 the CSS changes aren't drawn until the last update.
The easiest way to force a redraw is to read the elements height:
$("div[role='progressbar']").css("width", markerPer + "%").attr("aria-valuenow", markerPer);
$("div[role='progressbar']").height();