I have a simple progress bar with a JS animation. Every 20ms, the value of the progress bar increases by 0.25. So it takes 20*4*100ms = 8 seconds to complete the progress bar, as you can see on the JSFiddle below.
function updateProgress(currentValue, expectedValue){
var inProgress = setInterval(function() {
currentValue = currentValue + 0.25;
$('#progress-bar').attr('value', currentValue);
$('#progress-text').text(Math.round(currentValue));
if (currentValue == expectedValue) clearInterval(inProgress);
}, 20);
}
updateProgress(0, 100);
<progress id="progress-bar" value="0" max="100"></progress>
<div><span id="progress-text">0</span>%</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
So if I take the same code and start the progress bar at 50% instead of 0%, it will take 20*4*50ms = 4 seconds to complete the progress bar, as you can see on the JSFiddle below.
function updateProgress(currentValue, expectedValue){
var inProgress = setInterval(function() {
currentValue = currentValue + 0.25;
$('#progress-bar').attr('value', currentValue);
$('#progress-text').text(Math.round(currentValue));
if (currentValue == expectedValue) clearInterval(inProgress);
}, 20);
}
updateProgress(50, 100);
<progress id="progress-bar" value="50" max="100"></progress>
<div><span id="progress-text">50</span>%</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
I would like the function to always have the same time to execute, lookless of the starting value.
For example 0 to -> 100 : 4 seconds, and 50 to -> 100 also 4 seconds.
I tried this but it does not work :
function updateProgress(currentValue, expectedValue){
var interval = 4000 / (expectedValue - currentValue) / 4;
var inProgress = setInterval(function() {
currentValue = currentValue + 0.25;
$('#progress-bar').attr('value', currentValue);
$('#progress-text').text(Math.round(currentValue));
if (currentValue == expectedValue) clearInterval(inProgress);
}, interval);
}
updateProgress(50, 100);
I think I'd use requestAnimationFrame for this, this is pretty much what it's designed for.
function updateProgress(currentValue, expectedValue){
var valueDelta = expectedValue - currentValue,
startTime = performance.now();
nextFrame(startTime);
function nextFrame(time) {
var value = Math.min(expectedValue, currentValue + valueDelta * (time - startTime) / 4000);
setValue(value);
if (value !== expectedValue) {
requestAnimationFrame(nextFrame);
}
}
function setValue(value) {
$('#progress-bar').attr('value', value);
$('#progress-text').text(Math.round(value));
}
}
updateProgress(50, 100);
<progress id="progress-bar" value="50" max="100"></progress>
<div><span id="progress-text">50</span>%</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
Whether you choose to use requestAnimationFrame or setInterval, I think the key thing is to base your animation on the current time rather than assuming that the timer will be called on schedule.
When I run your original code it works fine for me. My guess would be that the problem is the timer not being called on schedule when the interval is very small, that will be platform dependent. setInterval is not super accurate at the best of times but for the full range, 0 to 100, you'll be relying on a 10ms timer, which is small enough to be problematic.
Related
I am working on simple script that should animate given value (for example 6345.23) to 0 by counting it down, it should also end up at 0 if specified amount of time have passed (for example 2 seconds.
I started by simple logic:
given config: initial value, time in sec, interval
time is given in seconds so convert it to milliseconds
calculate amount of ticks by dividing time in ms by interval
calculate amount of decreased value per tick by dividing initial value by amount of ticks
once above are known we can simply do: (simple model, not actual code)
intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
clearInterval(intId);
} else {
value -= amount_per_tick;
// update view
}
}, interval);
actual code:
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
Link do fiddle (in console you can observe how it works)
If you set time to 2 (2 seconds) its ok, but if you set time to for example 2.55 (2.55 seconds) it doesnt stop at all at 0, its passing by and going indefinitely in negative values.
How i can fix it so no matter what is set in seconds its always go precisly one by one until reaches perfectly 0?
var value = 212.45,
time = 2, // in seconds
interval = 20; // in milliseconds
var time_to_ms = time * 1000,
amount_of_ticks = time_to_ms / interval,
amount_per_tick = (value / amount_of_ticks).toFixed(5);
var start_time = new Date();
var ticks_made = 0;
var intId = setInterval(function() {
if(ticks_made === amount_of_ticks) {
console.log('start time', start_time);
console.log('end time', new Date());
console.log('total ticks: ', amount_of_ticks, 'decresed by tick: ', amount_per_tick);
clearInterval(intId);
} else {
value = (value - amount_per_tick).toFixed(5);
console.log('running', ticks_made, value);
}
ticks_made++;
}, interval);
You're relying on ticks_made === amount_of_ticks being an exact match. Chances are, due to rounding, you won't get an exact match, so you'd be better off doing:
if(ticks_made >= amount_of_ticks) {
kshetline's answer correctly addresses why you get into negative values. When dealing with fractional IEEE-754 double-precision binary numbers (in the normal range, or even whole numbers in very high ranges), == and === can be problematic (for instance, 0.1 + 0.2 == 0.3 is false). Dealing with values as small as the fractional values here are, accumulated imprecision is also a factor. It's inevitable to have to fudge the final step.
But there's a larger issue: You can't rely on timers firing on a precise schedule. Many, many things can prevent their doing so — other UI rendering work, other scripts, CPU load, the tab being inactive, etc.
Instead, the fundamental technique for animation on browsers is:
Update when you can
Update based on where you should be in the animation based on time, not based on how many times you've animated
Use requestAnimationFrame so your update synchronizes with the browser's refresh
Here's your code updated to do that, see comments:
// Tell in-snippet console to keep all lines (rather than limiting to 50)
console.config({maxEntries: Infinity});
var value = 212.45,
time = 2.55, // in seconds
time_in_ms = time * 1000,
amount_per_ms = value / time_in_ms,
interval = 100 / 6, // in milliseconds, ~16.66ms is a better fit for browser's natural refresh than 20ms
ticks_made = 0;
// A precise way to get relative milliseconds timings
var now = typeof performance !== "undefined" && performance.now
? performance.now.bind(performance)
: Date.now.bind(Date);
// Remember when we started
var started = now();
// Because of the delay between the interval timer and requestAnimationFrame,
// we need to flag when we're done
var done = false;
// Use the interval to request rendering on the next frame
var intId = setInterval(function() {
requestAnimationFrame(render);
}, interval);
// About half-way in, an artificial 200ms delay outside your control interrupts things
setTimeout(function() {
console.log("************DELAY************");
var stop = now() + 200;
while (now() < stop) {
// Busy-loop, preventing anything else from happening
}
}, time_in_ms / 2);
// Our "render" function (okay, so we just call console.log in this example, but
// in your real code you'd be doing a DOM update)
function render() {
if (done) {
return;
}
++ticks_made;
var elapsed = now() - started;
if (elapsed >= time_in_ms) {
console.log(ticks_made, "done");
done = true;
clearInterval(intId);
} else {
var current_value = value - (amount_per_ms * elapsed);
console.log(ticks_made, current_value);
}
}
/* Maximize in-snippet console */
.as-console-wrapper {
max-height: 100% !important;
}
If you run that, then scroll up to the "************DELAY************" line, you'll see that even though rendering was held up by "another process", we continue with the appropriate next value to render.
It would make sense to convert the result of .toFixed() to a number right away:
let amount_per_tick = +(value / amount_of_ticks).toFixed(5);
let value = +(value - amount_per_tick).toFixed(5);
(note the + signs)
Then you will never have to worry about type coercion or anything, and instead just focus on math.
What I am looking to do is have a number start counting either up or down, lets say from 1 to a 100 or a 100 to 1. I have found many many plugins that do this but they all have a duration variable. The problem is that duration variable applies to how long it takes to get from start to finish. I am looking for a way to set how long it takes to get to the next digit. IE counting from X to Y and updating the number every .25 seconds. This way a larger number would take longer to get to than a smaller number.
This plugin looks great but I can't get it to do what I want.
https://github.com/mhuggins/jquery-countTo
Any help would be great.
Please try this and let me know if that's what you want:
function countTo(min, max, interval, callback) {
var counter = min;
var i = setInterval(function() {
callback(counter);
counter++;
if(counter> max) return clearInterval(i);
}, interval*1000);
};
countTo(0, 100, 0.25, function(counter) {
$("#counter").text(counter);
});
countTo(0, 100, 0.01, function(counter) {
$("#counter2").text(counter);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="counter"> </div>
<div id="counter2"> </div>
You can try something like this
function count(target, start, end, step, timeInterval) {
var timer = setInterval(function() {
if (start == end) {
clearInterval(timer);
return false;
}
start += step;
target.html(start);
}, timeInterval);
}
count($('#timer1'), 1, 100, 1, 50);
count($('#timer2'), 100, 1, -1, 50);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="timer1"></div>
<div id="timer2"></div>
currentValue = 708295;
targetValue = 0;
function count() {
if (currentValue > targetValue) {
currentValue -= 1
} else if (currentValue < targetValue) {
currentValue += 1
}
document.getElementById('timeTo').innerHTML =
'Total wordcount:'+ currentValue.toString();
changeTime = 1000;
if (Math.abs(currentValue - targetValue) <0) {
changeTime = 1000 - Math.abs(currentValue - targetValue);
}
setTimeout(count,changeTime/1);
}
count()
<h1 id="timeTo">Starting</h1>
any help will be very useful, i m trying to learn javascricpt and i want to create count down.
I want number to go down every sec but so far i have created the function to count the number but its counting too fast
ANy help or any suggestions
Thanks
setTimeout second argument is miliseconds value, it should be set to 1000 if you want to tick every second.
I am trying to create a countdown that starts at 100 and ends at 30.
I want the beginning of the countdown to be really fast and then slow down and stop at 30. At the moment though the "path" of the countdown is linear.
Also to make you aware, the start number and end number may change, but still require a curved time effect.
https://jsfiddle.net/ygnvav7c/
$(document).ready(function() {
var timer;
var count=100;
var ms = 1;
var step = 5;
var counter = setTimeout(timer, ms);
Countdown();
function Countdown() {
count=count - 1;
if (count >= 30) {
$('#countdown-display').html(count);
ms = ms + step;
counter = setTimeout(Countdown, ms);
}
}
});
How can I use Math.PI to make the time "curve"?
Parameterize your count variable with some number 0 <= t <= 1. Increment this with a regular interval (say 0.01)
e.g. for a quadratic decay:
count = count_start + (count_end - count_start) * (1 - t) ^ 2
For a sine-curve decay:
count = count_start + (count_end - count_start) * sin(pi * t / 2)
For an exponential decay:
count = count_start + (count_end - count_start) * (1 - k ^ t) / (1 - 1 / k)
where k > 1
As much as I appreciate the solutions of my dear collegues but the requirement was that it stops at the end. So the time must go to infinite or at least close enough.
Taking 10^6 as "close enough" and the tangent function as the means to reach infinity (but not to go beyond) together with a bit of simple algebra and taking the liberty to change the counting function from decreasing to increasing we get the following to play with-not to to forget the complete ignorance of teh good ol' Chicago Manual of Style by using an infamously long and winded run-on sentence, that is additionally peppered with a lot of redundant additions.
tl;dr: We need to get the x in tan(x) as close to PI/2 as possible but not bigger to reach that goal.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Counter</title>
<script type="text/javascript">
var timer;
var counter = setTimeout(timer, ms);
var ms = 1;
var count_start = 30;
var count_end = 100;
var count = count_start;
// roughly (atan(10^6) - .02) / 68
var step = 0.0228058;
// to avoid zero
var start = 0.02;
var brk = document.createElement("br");
function Countdown() {
var out = document.getElementById("output");
var text;
if (++count < count_end) {
start += step;
ms = Math.tan(start) * count;
counter = setTimeout(Countdown, ms);
text = count.toString() + " - " + ms.toString();
text = document.createTextNode(text);
out.appendChild(text);
out.appendChild(brk.cloneNode());
}
}
</script>
</head>
<body onload="Countdown()">
<p id="output" > </p>
</body>
</html>
Reducing the value in step makes the curve end earlier (you kind of "zoom in"), e.g.: step = 0.02 lets the final step last about half a second and about 85ms with step = 0.01.
Please Help! I'm new to Javascript, so there's probably an easier solution to this. Basically, I need it to prompt for a number and then count down from that number to zero. Once it reaches zero, I need it to count-up and stop at the same prompted number.
I got it to count down at first, then I completely butchered it, I have no clue what to do.
<script type="text/javascript">
// get number from user
var startNum = parseInt(prompt("Input a number to start counting down from.",""));
var counter = setInterval(timer, 1000);
console.log(startNum);
function timer() {
startNum--; // reduce number by 1
console.log(startNum);
if (startNum <= 0) {
clearInterval(counter);
}
}
var counter = setInterval(timer2, 1000);
var endNum = 0
function timer2() {
console.log(endNum)
endNum++; // add number by 1
console.log(endNum);
if (endNum >= startNum) {
clearInterval(counter);
}
}
</script>
You've got a couple issues here. the first one was pointed out by Rob in the comments. You're running both functions at the same time.
The other issue you have is that you're never storing the number. You're just subtracting and adding to nothing essentially.
So -
<script type="text/javascript">
// get number from user
var startNum = parseInt(prompt("Input a number to start counting down from.",""));
var currentNum = startNum;
var counter = setInterval(timer, 1000);
function timer() {
console.log(currentNum);
currentNum -= 1; // reduce number by 1
console.log(currentNum);
if (currentNum == 0) {
clearInterval(counter);
counter = setInterval(timer2, 1000);
}
}
function timer2() {
console.log(currentNum)
currentNum += 1; // add number by 1
console.log(currentNum);
if (currentNum == startNum) {
clearInterval(counter);
}
}
</script>
Something like this should do the trick. Basically creating another variable to hold your start number and consider that the current number and the value that is going to change.
here's a fiddle - http://jsfiddle.net/w5FM6/
cheers