Why doesn't Progress Bar dynamically change unlike Text? - javascript

I'm dynamically updating a few elements after a setTimeout() function. The jQuery function .text() seems to dynamically update with each change of index of an array while processing. But a bootstrap progressbar which is being changed through .css() and .attr() doesnt seem to dynamically update. Here is my page : http://imdbnator.com/process.php?id=f144caf0843490c0d3674113b03da0c5&redirect=false
You can see that the text gets changed but the progress bar only finishes after the whole setTimeout() function finishes. Also, if I set the delay = 1000. It works. But it slows down by application. Therefore, I need delay = 0. But why doesnt the progressbar change?
Here is my snippet
function launch(a) {
var inc = 0;
var maxc = a.length;
var delay = 0; // delay milliseconds
var iID = setInterval(function () {
var index = inc;
var movie = a[inc];
//start processing function
//styling while processing
var markerPer = ((index + 1) / rawListNum) * 100; // marker percentage
$("#procNum").text("(" + (index + 1) + "/" + rawListNum + ")"); //Processing number
$("#procMovie").text(movie); //Processing Name
$("div[role='progressbar']").css("width", markerPer + "%").attr("aria-valuenow", markerPer); // progress bar -> DOES NOT WORK
if (++inc >= maxc) clearInterval(iID);
},
delay);
}

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.
Demo in Stack Snippets
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>

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

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>

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();

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);

Related

Fade In and Fade Out using pure Javascript in a simple way

I've been trying to create a fadeIn & fadeOut animation using VanillaJS in my project but I literally don't understand what's the problem. I'm using SCSS. I made it simple for you.
I tried visibility but it didn't work too. like it appears e.g. for 200ms but then immediately disappears. In another way of explanation, it appears whenever I click on it (stable) and then goes away after 200ms (unstable).
const fade = () => {
const box = document.querySelector('#box');
box.classList.toggle('fade');
};
document.querySelector('#fadebtn').addEventListener('click', fade);
#box {
width: 70px;
height: 50px;
background: #FD7A6B;
display: none;
opacity: 0;
-webkit-transition: 200ms ease-in-out;
-moz-transition: 200ms ease-in-out;
-o-transition: 200ms ease-in-out;
transition: 200ms ease-in-out;
}
#box.fade {
display: block !important;
opacity: 1 !important;
}
// I also tried this, wondered it may work, but didn't.
// .fade {
// display: block !important;
// opacity: 1 !important;
// }
<button type="button" id="fadebtn">Fade</button>
<div id="box"></div>
I wrote this due to the title of the question: "Fade in ... pure javascript ... simple way."
tl;dr https://jsfiddle.net/nqfud4j0/
The following solution is a basic example of how you can use only Javascript to fade in/out to a desired value. You could also use this with other values/properties, but it also serves as an example for basic tweening.
It's intentionally using setInterval rather than requestAnimationFrame to demonstrate the example's use of time + controlled framerate rather than a delta or 'fast as possible.' A good solution would abstract this logic into a tweening library that combines both RAF + intervals to manage latency between frames.
function fadeTo(element, toValue = 0, duration = 200) {
// Store our element's current opacity (or default to 1 if null)
const fromValue = parseFloat(element.style.opacity) || 1;
// Mark the start time (in ms). We use this to calculate a ratio
// over time that applied to our supplied duration argument
const startTime = Date.now();
// Determines time (ms) between each frame. Sometimes you may not
// want a full 60 fps for performance reasons or aesthetic
const framerate = 1000 / 60; // 60fps
// Store reference to interval (number) so we can clear it later
let interval = setInterval(() => {
const currentTime = Date.now();
// This creates a normalized number between now vs when we
// started and how far into our desired duration it goes
const timeDiff = (currentTime - startTime) / duration;
// Interpolate our values using the ratio from above
const value = fromValue - (fromValue - toValue) * timeDiff;
// If our ratio is >= 1, then we're done.. so stop processing
if (timeDiff >= 1) {
clearInterval(interval);
interval = 0;
}
// Apply visual. Style attributes are strings.
element.style.opacity = value.toString();
}, framerate)
}
// Element reference
const element = document.querySelector('div');
// Fade in and out on click
element.addEventListener('click', e => {
// Animates our element from current opacity (1.0) to 0.25 for 1000ms
fadeTo(element, 0.25, 1000);
// Waits 1s, then animates our element's opacity to 1.0 for 500ms
setTimeout(() => {
fadeTo(element, 1.0, 500);
}, 1000);
});

Javascript: How to create a smooth rotation animation for an image? [Compass needle]

I like to build a compass app for mobile devices. I've already created the function that returns the change-values for the compass-needle rotation.
(e.g. 20) But if I just set the 'image-transform' to 'rotate(x deg)' it looks a bit jumpy, so my idea was to add a smooth animation for the needle to move.
How to code a js-function that can add a animation for my compass needle?
IMPORTANT: What happens when the function is called with parameter '20', and 0.5 second (when the turn-20-onesecond animation isn't over yet) again called with a different parameter '-30'. SO one feature of the function must be to cancel the running animation and adds to the old change-value('20') the new change-value('-30') and starts another animation.
Hope you could understand my problem. Here my basic function:
var change_value=20;
function start_animation(change_value){
$needle_element.css("-webkit-transform", "rotate(" + change_value + "deg)")
}
Is this what you are looking for?
// Get references to DOM elements
var input = document.getElementById("angle");
var btn = document.getElementById("btnGo");
var needle = document.getElementById("needle");
// Set up click event handler
btn.addEventListener("click", start_animation);
// Storage for the previous angle
var lastAngle = "";
function start_animation(){
// Update the total angle needed
lastAngle = +lastAngle + +input.value;
// For testing:
console.clear()
console.log("Current total angle: " + lastAngle);
// Move the needle:
needle.style.transform = "rotate(" + lastAngle + "deg)";
}
#needle {
height:5px;
background-color:#808080;
width:100px;
margin-top:100px;
/* Initial value for rotate is needed for transition to work */
transform:rotate(0);
/* CSS transition creates smooth effect */
transition:all 1.5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="angle" value="20"><button type="button" id="btnGo">Go!</button>
<div id="needle"></div>

SetTimeout inside a for loop

I'm trying to write a script that changes the z-index of 3 images. Basically the script should target the current image and apply a higher z-index on the next image, like a sort of carousel but with a z-index rather then active class. The challenge is to set the z-index after a specific interval. The problem is that the first image is displayed and then the last one. This is my code:
Html:
<div class="changingimages">
<img src="#" data-time="3000" width="100%" class="alternateimage alternateimage1">
<img src="#" data-time="2000" width="100%" class="alternateimage alternateimage2">
<img src="#" data-time="4000" width="100%" class="alternateimage alternateimage3">
</div>
jQuery Script
<script type="text/javascript">
jQuery(document).ready(function(){
var changeImg = function(i, time, currentImg) {
setTimeout(function(){
jQuery(currentImg).next().css("z-index", i);
}, time);
};
var numberOfChilds = jQuery(".changingimages").children().length;
var currentIndexClass;
var currentImg;
var time;
for (var i=1; i<=numberOfChilds; i++) {
currentIndexClass = '.alternateimage' + i;
currentImg = jQuery(currentIndexClass);
time = jQuery(currentIndexClass).attr("data-time");
changeImg(i, time, currentImg);
}
});
I think there is some problem with the closure inside a loop, but not sure!
It's a common misconception that setTimeout schedules events to run relative to previously queued events. It looks like you believe that, theoretically, the following:
setTimeout(f, 100);
setTimeout(g, 100);
setTimeout(h, 100);
would result in a timeline like this:
0ms Start
100ms Run f()
200ms Run g()
300ms Run h()
The reality is that the time option in setTimeout means "run this function after at least this much time has passed." Going off of the previous example, you would actually get something more like
0ms Start
100ms Run f()
101ms Run g()
102ms Run h()
To space out your code correctly, keep adding to the timeout time rather than replacing it.
var time = 0;
for (var i = 1; i <= numberOfChilds; i++) {
currentIndexClass = '.alternateimage' + i;
currentImg = jQuery(currentIndexClass);
// Add to the previous time
time += parseInt(jQuery(currentIndexClass).attr("data-time"), 10);
changeImg(i, time, currentImg);
}
Here is a fiddle implementing the use of timeout to achieve what you want.
fiddle
.textArea {
position: absolute;
height: 50px;
width: 50px;
display: block;
}
.box_a {
background-color: blue;
}
.box_b {
background-color: red;
}
.box_c {
background-color: orange;
}
.active {
z-index: 3;
}
<div class="textArea box_a active">a</div>
<div class="textArea box_b">b</div>
<div class="textArea box_c">c</div>
$(function(){
var $elem = $('.textArea');
timeout(0);
function timeout(i){
$($elem[i]).addClass('active');
return setTimeout(function(){
$elem.removeClass('active');
i++;
if(i >= $elem.length){
i = 0
}
timeout(i);
}, 1000)
}
});
Note it does not use a for loop, because timeout is asynchronous and will not execute sequentially. Each timeout will fire at the same time basically, then do their action based on the wait time.
The solution is to make a function that keeps track of the index, and when the last timeout has completed execution.

How To Make a Hipmunk Style Progress Bar with Twitter Bootstrap and Jquery

To my surprise, I'm not seeing much for this.
The bootstrap docs give plenty of options for displaying a progress bar, but no instructions for actually making it do something.
I'm writing a one page web app that'll ideally use a progress bar transition before switching to the hidden part of the page.
Here's the simplified html:
HTML
<div id="part1">
<p>Sample App</p>
<button class="analyze btn btn-primary">Analyze</button>
</div>
<div id="part2">
<!-- Some html goes here -->
</div>
CSS
#part2 {
display: none;
}
Jquery
$(".analyze").click(function() {
$("#part1").hide();
$("#part2").show();
});
This is very simple. What I'd like to do is make a progress bar that dynamically populates on $(".analyze").click and takes a fixed amount of time to complete before #part2 becomes visible using the bootstrap progress bar.
Something very similar to what hipmunk, or many of the other airline aggregator sites do.
Ideally this is compatible across most browsers and uses jquery since this is what I'm using to make most of the UI for my app.
You can make use of the JavaScript function setInterval to repeatedly run a function over and over again. So that is an easy way to change the width of something over a given amount of time.
HTML
<div id="instance" class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
<span class="sr-only">0% Complete</span>
</div>
</div>
<div class="show-on-done hidden">here is some other stuff</div>
JavaScript:
function fakeProgress(container, durationInMs, onDone) {
var intervalInMS = 200;
var doneDelay = intervalInMS * 2;
var bar = container.find('.progress-bar');
var srOnly = bar.find('.sr-only');
var percent = 0;
var interval = setInterval(function updateBar() {
percent += 100 * (intervalInMS/durationInMs);
bar.css({width: percent + '%'});
bar['aria-valuenow'] = percent;
srOnly.text(percent + '% Complete');
if (percent >= 100) {
clearInterval(interval);
setTimeout(function() {
if (typeof onDone === 'function') {
onDone();
}
}, doneDelay);
}
}, intervalInMS);
}
Then call it with this JavaScript:
var duration = 1000; // in milliseconds
function onDone() {
$('.show-on-done').removeClass('hidden');
}
fakeProgress($('#instance'), duration, onDone);
JSFiddle: https://jsfiddle.net/6ht5umxz/3/

How to get width from animated div in jQuery

Thanks in advance for your support here. I have a simple code here but I wonder why it doesn't work.
This code below was put into a variable "percent"
var percent = $('#div').animate({width:80 + '%'},1000);
and I want to use the variable to become a text or content to a specific div. I tried to do it this way, however it doesn't work.
$('#val').text(percent); or $('#val').html(percent);
I tried to use the parseInt() function from JavaScript, but it doesn't work as well.
$('#web-design').text(parseInt(percent));
Do you have any suggestions or is this possible?
I am not sure if the line code it is correct
var percent = $('#div').animate({width:80 + '%'},1000);
To find the with property for #div I will do something like this
$('#val').text($("#div").css("width"))
or
$('#val').text($("#div").width())
The animate function does not return the changes to the element. To get the width you will have to use .width() but you will get it in pixel and not percentage.
var width = $('#div').width();
You can try this code:
$('#div1').animate({width:80 + '%'},{
duration:1000,
easing:'swing',
step: function() { // called on every step
$('#div1').text(Math.ceil($('#div1').width()/$('#div1').parent().width()*100) + "%");
}
});
This will print the Div-Width % as text in Div at every step of update.
You code is going to animate to 80% width in 1 second (1000 ms). You can increase the percentage width by 1% each iteration and update your #val at the same time until you hit 80. Below is the example based on 10000ms (10 secs), you can change the starting parameters such as duration, endWidth, startWidth to suit.
Demo: http://jsfiddle.net/zthaucda/
HTML:
<div class="my-anim-div"></div>
<button class="start-animating">Start animation</button>
<div id="val">Width will display here</div>
CSS:
.my-anim-div {
width:0%;
height:200px;
background-color:red;
}
Javascript including jQuery library:
var duration = 10000;
var endWidth = 80;
var startWidth = 0;
$('.start-animating').on('click', function () {
// work out the interval to make the animation in `duration`
var interval = duration / (endWidth - startWidth);
console.log(interval);
var myInterval = setInterval(function () {
// Increment the startWidth and update the width of the div and the display
startWidth++
// update div width
$('.my-anim-div').animate({
'width': startWidth + '%'
}, interval);
// update display
$('#val').text(startWidth + '%');
if (startWidth == 80) {
// then exit the interval
clearInterval(myInterval);
}
}, interval);
});
If you mean you want to get the current width of the div you can use clientWidth property:
$('#div')[0].clientWidth

Categories

Resources