Increment Value over a Duration on Update - javascript

I'm trying to make a script to increment a number to the selected number on every select change. For example, if the user selects 30, I want the number to increase at a rate of 30 values per second so it reaches 30 in one second.
I don't know what went wrong, but when executing this script, it only increments on the first page load but with no value change.
https://jsfiddle.net/User1010/b7znc3fL/
var valueElement = document.getElementById('value');
var option = document.getElementById('option');
var start = 0;
var end = parseFloat(option.innerHTML);
var duration = 1000; // In milliseconds (divide by 1000 to get seconds).
var framerate = 50; // In milliseconds (divide by 1000 to get seconds).
var toAdd = ( ( end - start ) * framerate ) / duration;
var interval = setInterval(function() {
var currentValue = parseFloat(valueElement.innerHTML);
if (currentValue >= end) {
clearInterval(interval);
return;
}
valueElement.innerHTML = (!isNaN(currentValue) == true ? (currentValue + toAdd).toFixed(2) : toAdd);
}, framerate);

You may be overthinking this task. I also found there were errors and things to change in the console and the JSFiddle. For example, there is no element with the name option.
https://www.khanacademy.org/computing/computer-programming/html-css-js/html-js-dom-animation/p/animating-dom-with-setinterval
https://www.khanacademy.org/computer-programming/spin-off-of-challenge-stopwatch/6144204027232256
Let's start with something basic: A Stopwatch
Define variables for better convenience, for better, more common practice, and for higher efficiency
A variable that can be called counterEl initializing the span element using document.getElementById() on the id 'daily'.
A variable that can be called selectEl initializing the select element using document.getElementById() on the id 'value'.
A type null variable that can be called currentTime which will turn counterEl into a float data type by calling parseFloat() on countEl.textContent.
A type null variable called stopwatch that will be initialized when you use setInterval.
I also used the linking Stack Overflow question for help
Add an event listener to the select element for every time its value changes like so: selectElement.addEventListener("change", myFunction);
Create a global function resetStopwatch() {}
Set countEl.textContent to 0.
Just for good measure, set currentTime to 0 as well.
stopwatch = window.setInterval(countUp, 1000);
Create the global countUp function
Everything here is explained in the comments.
// Turns the value into a float so it can be incremented and compared (textContent is a string)
currentTime = parseFloat(seconds.textContent);
// Add 1 second every time function is called
seconds.textContent = currentTime + 1;
if (seconds.textContent >= selectElement.value) {
window.clearInterval(stopwatch); // Stops the stopwatch if the seconds
reached the selected option
console.log("My time has been cleared");
}
Now let's slightly tweak this to make it a 'reverse stopwatch'
In the setInterval, you want it to increment that many in one second, so you would change the invocation to
stopwatch = window.setInterval(countUp, 1000/incrementRate.value);
Use my JS Fiddle for guidance in solving your problem:
https://jsfiddle.net/404_Error/z0t4spob/

Looks like you just need to bind a change event handler to your select/option.
Reference MDN's Event documentation on adding this to your script to handle the changes and update of the value.
Just a heads up, if you want to use a framework like jQuery, the process and script can be simplified drastically.

Related

Simple issues with setInterval

so sorry for a basic qn like this. This is my simple JS code to do a timer. I was hoping to print the countdown number periodically. I tested the rest of the code and it seems to work, however it gives me sth weird when I add the setInterval command. I am not sure why. I hence seek an explanation and how to correct it.
Also, when it works, the new reloaded number should replace the old number right. For instance when 4 appears, it simply replaces 5 during the countdown.
Code:
var x = prompt("Time till take off");
function printTimer (){
document.write(x)
}
while (x > 0) {
setInterval(printTimer,1000)
x = x -1;
}
if (x=1){
document.write("Rocket taken off")}
Thanks!
The following snippet I think does what you're looking for. Further explanation is in the code comments for a little context:
var output = document.getElementById('count-down');
/**
* Initiates a countdown from given time in seconds
* #param {number} count
*/
function countDown(count) {
// Create the interval and save it in a variable 'interval'.
// We need it later when the countdown reaches 0
var interval = setInterval(function writeCount() {
// If time till takeoff is greater than 0, we print the current
// count and then decrement the count, so that next time count
// will be ( count - 1 )
if (count > 0) {
output.innerText = count;
count--;
// OR: count -= 1';
// OR: count = count - 1';
// are all equivalent
}
// Otherwise we write a final value, and clear the timeout
// so that we only get this final value once. If not cleared
// the next second count will be -1, and this else block would
// be re-executed.
else {
output.innerText = "Rocket taken off";
clearTimeout(interval);
}
}, 1000);
}
countDown(prompt("Time till take off"));
<div id="count-down"></div>
I think #jeffrey-westerkamp's solution is elegant and the way to go to achieve your result. However, if you are curious why your current code isn't working, here's what's happening.
There are a 3 big issues.
You are using setInterval instead of setTimeout.
setInterval will call a function repeatedly, until it is cancelled, at the specified time interval.
This would mean that if the rest of your code was working, it would output "10, 10, 10, 10, 10, 10, 10..." "9, 9, 9, 9, 9, 9, 9.." etc., until you cancelled each number's interval.
setTimeout calls the specified function once at the specified amount of time in the future.
You need to bind the variable x to your call to each setTimeout.
Right now, each time printTimer is called, it looks at the value of x. But here's the thing: the value of x is going to be 0 for each call.
Why? The while loop queues up all the calls to setTimeout (or in your case setInterval. At the specified time in the future, printTimer gets called. When it does, it looks for the variable x. By the time the first printTimer call runs, x has long since been set to zero from the while loop.
You need to make the delay at which setTimeout is called dependent on the position in the countdown sequence.
A for loop makes this a little bit more intuitive. Something like this:
function printTimer(count) {
if (count===1){ console.log("Rocket taken off"); }
else { console.log(count); }
}
for (var i=0;i<x;i++) {
(function(count){
setTimeout(printTimer.bind(null,x-count),1000*count);
})(i);
}
That strange syntax inside the for loop is called an IIFE, or an immediately invoked function expression. Another way to write that for loop without the IIFE would be using let instead of var:
for (let i=0;i<x;i++) { //also works
setTimeout(printTimer.bind(null,x-i),1000*i);
}
If this is confusing, check out this section of You Don't Know JS: Loops+Closure.

Creating a series of timed form submissions

I am interested in designing a timed quiz questions which submit themselves after 30 seconds or so. Following this other SO request I have coded the following:
<script>
var counter = 30;
var interval = setInterval(function() {
counter--;
// Display 'counter' wherever you want to display it.
document.getElementById("counter").innerHTML = "Timer:"+counter
if (counter == 0) {
// Submit form
test.submit('timeout');
}
}, 1000);
</script>
<p id="counter"></p>
This seems to work on most modern browsers. There is a problem however that this seems to work fine for the first question or form. I think the second form defines an additional function which causes the countdown to go twice as fast. Then the third, three times as fast, etc. Is there a way to ensure that this function is only defined once?
Thanks for any help you can provide. I am new to javascript so I apologize in advance if I have used the wrong terminology.
The first time you call setInterval(), it defines an interval that will decrement the counter by 1 every second. It doesn't stop once the counter reaches 0; it just keeps decreasing it every second. Then you call setInterval() again, which sets up another decrement of the same counter every second. So now the counter gets decremented twice per second: once because of the first interval you set up and another time because of the second interval. The effect just builds up as you add more intervals.
You can see the effect in this fiddle.
The solution is just to stop the interval once the counter reaches 0, before you set up another interval. Besides, there's no need to use the same counter variable for all the different intervals, so you can just declare a new variable each time in a narrower scope. Narrowing the scope of variables will minimize the risk of different pieces of code interfering with each other.
function startCountDown(){
// This counter is local to this invocation of the "startCountDown"
// function.
var counter = 10;
var interval = setInterval(function() {
counter--;
// Display 'counter' wherever you want to display it.
document.getElementById("counter").innerHTML = "Timer:"+counter
if (counter == 0) {
// Submit form
console.log("Form submitted!");
// Stop this interval so that it doesn't update the
// interface anymore (next interval will take care of that).
clearInterval(interval);
startCountDown();
}
}, 1000);
}
startCountDown();
This other fiddle shows the solution.

How to trace in the console how many intervals are being executed - JavaScript

I have a problem trying to know if a setInterval() is taking place or it was killed.
I am creating an interval and saving it into a variable:
interval = setInterval('rotate()',3000);
Then on click to an element I stop the interval and wait 10 seconds before starting a new one, by the way the variable interval is global:
$(elm).click(function(){
clearInterval(interval);
position = index;
$('#banner div').animate({
'margin-left':position*(-img_width)
});
setTimeout('startItnerval()',10000);
});
function startItnerval(){
interval = setInterval('rotate()',3000);
}
It seems to work but eventually I can realize that there are intervals still being in place, everytime I start a new interval it is saved in the interval variable, which is global, so in theory even if I start 100 intervals they are all saved in the same variable replacing the previous interval right? So I should only have one instance of interval; then on clearInterval(interval); it should stop any instance.
After looking at the results, apparently even if it is saved in the same variable, they are all separate instances and need to be killed individually.
How can I trace how many intervals are being executed, and if possible identify them one by one? even if I am able to solve the problem I really would like to know if there is a way to count or show in the console how many intervals are being executed?
thanks
jsFiddle Demo
As pointed out in comments, the id's constantly increase as timers are added to a page. As a result, it may be possible to clear all timers running on a page like this:
function clearTimers(){
var t = window.setTimeout(function(){
var idMax = t;
for( var i = 0; i < idMax; i++ ){
window.clearInterval(i);
window.clearTimeout(i);
}
},4);
}
The reason that you can only see one interval is because every time you start a new interval, you overwrite the value in interval. This causes the previous intervals to be lost but still active.
A suggestion would be to just control access to your variable. Clearly there is an issue where the start function is called too often
clearInterval(interval);//when you clear it, null it
interval = null;
and then take advantage of that later
if( interval != null ){
interval = setInterval('rotate()',3000);
}
Also, as Pointy noted in a comment, using a string to call a function is not best practice. What it basically does is converts it into a Function expression which is similar to using eval. You should probably either use the function name as a callback
setInterval(rotate,3000);
or have an anonymous function issue the callback
setInterval(function(){ rotate(); },3000);
setInterval returns an Id, not the actual object, so no, no interval will be overriden if you repeat the line
var xy = setInterval(function() {...}, 1000);
If you want to stop the interval you have to clear it:
clearInterval(xy);
And if your startInterval can be called multiple times in a row, but you don't want to create multiple intervals, just clear the inverval before you start a new one:
function startInterval(){
clearInterval(interval);
interval = setInterval('rotate()',3000);
}
If you have to create multiple intervals, you could save the ids in an array to keep track of them:
var arr = [];
//set the interval
arr.push(setInterval(...));
//get number of currently running intervals
var count = arr.length //gives you the number of currently running intervals
//clear the interval with index i
clearInterval(arr[i]);
arr.splice(i, 1);

why jquery can't animate number accurately?

i am trying to use the following code to increment number in a textbox
// Animate the element's value from 0 to 1100000:
$({someValue: 0}).animate({someValue: 1100000}, {
duration: 1000,
step: function() { // called on every step
// Update the element's text with value:
$('#counterx').text(Math.floor(this.someValue+1));
}
});
it is working with small numbers like from 0 to 100
but when it comes to large number like in the mentioned code,
it is not giving the target number,
it is animating to numbers like 1099933 or 1099610 or .....
and every time it changes.
so how can i make it to animate to the number i specify?
I have the same issue. The reasoning is because animate function uses a mathematical formula that is time based. You don't really notice this when animating something css based because close enough in pixels is good enough. It will get close to the final value but may not always be exactly the end value. Solution is to use the complete event to set that last value.
Here is what you need to do:
function animateNumber(ele,no,stepTime){
$({someValue: 0}).animate({someValue: no}, {
duration: stepTime,
step: function() { // called on every step. Update the element's text with value:
ele.text(Math.floor(this.someValue+1));
},
complete : function(){
ele.text(no);
}
});
}
animateNumber($('#counterx'),100,10000);
animateNumber($('#countery'),100,1000)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
counterx(slow): <span id=counterx>--</span>
<br/>
countery(fast): <span id=countery>--</span>
1) Javascript is a single threaded application. Timeouts and animations ONLY push the event to the end of the stack based on an ideal stacking order. A long running section of script can cause the actual firing time of that event well past the accuracy you are looking for.
2) Animation approximates how much to increment, and on larger numbers that resolution is very inaccurate.
3) jQuery only has one animation buffer. You might run into some serious rendering issues if you invoke more than one "counter" using animation. Make sure to stop the previous animation before making any adjustments that effect it.
4) Even with a timeout of 0, you can expect the real world delay of ~15. Even if that is the only "thread" you have running.
Solution:
take a snapshot of the DTG
set your interval to something within the human experience, say ~200
on each interval, check how much time has passed from the original DTG
set your text field to that delta number.
stop the interval with the original DTG + "your target number" > the new DTG
Animate is not designed to increment a counter as text (though it may work by accident, which could change with any new version of jQuery), it's designed to animate one or more CSS properties. You should be using setInterval instead.
http://jsfiddle.net/jbabey/mKa5r/
var num = 0;
var interval = setInterval(function () {
document.getElementById('result').innerHTML = num;
num++;
if (num === 100) {
clearInterval(interval);
}
}, 100);​
Here's a solution that doesn't use .animate().
DEMO: http://jsfiddle.net/czbAy/4/
It's just a linear modification; you don't get the easing options if that's what you were after.
var counterx = $('#counterx'), // cache the DOM selection! :)
i = 0,
n = 1100000,
dur = 1000, // 1 second
int = 13,
s = Math.round(n / (dur / int));
var id = setInterval(function() {
counterx.text(i += s);
if (i >= n) {
clearInterval(id);
counterx.text(n);
}
}, int);
Here is a jquery plugin to animate numbers reliably, ut uses the complete callback to set the correct final number once the animation has finished:
https://github.com/kajic/jquery-animateNumber

Add a countdown in my scoring script (javascript/jquery)

I have the following script in a js file:
// Ad score
var score = 0;
//$('#score').text(score);
function foundMatchingBlocks(event, params) {
params.elements.remove();
score += 100;
$('#score').text(score);
};
Now on each matching, 100 points are added to var score. This all works. Now I want to extend this a bit. As soon as the page loads I want to start a countdown to reduce the number of points (starting with 100) with 1 point a second for 60 seconds. So the minimum number of points a user can get is 40. When someone gets the points, the counter should reset and countdown again.
Example:
Page loads (timer starts from 100)
User has a match after 10 seconds (+90 points are added)
Counter resets and countdown from 100 again
User found a match after 35 sec (+65 points are added)
etc etc
Problem is, I have no idea how to do this :( Hope someone can help me with this.
The above is fixed, thanks all for helping!!!
The big picture is, you'll need to become pretty familiar with timeouts and intervals in javascript. This is the reference page I keep going back to when I need to refresh my memory: http://www.elated.com/articles/javascript-timers-with-settimeout-and-setinterval/
For your specific task, you'll probably want to use an Interval that triggers every 1000 milliseconds to calculate the second-by-second point reduction, and a separate Timeout for failure that resets every time the user completes their challenge.
Here are a few tips for working with timeouts and intervals that usually lead to followup questions:
When you set a timeout, always capture the return value (I think it's basically a random integer). Save it to some global var for convenience.
var failureTimer; // global var high up in your scope
failureTimer = setTimeout ( "gameOver()", 100000 ); // 100 seconds * 1000ms
Then in whichever method gets called when the player completes their challenge, you call this:
clearTimeout (failureTimer); // resets the timer and gives them another 100 seconds
failureTimer = setTimeout ( "gameOver()", 100000 ); // yes call this again, to start the 100 sec countdown all over again.
The second pain point you're likely to encounter when working with Timeouts and Intervals is how to pass parameters to the functions like gameOver() in my example above. You have to use anonymous functions, as described here:
Pass parameters in setInterval function
For more on anonymous functions, this is a good overview:
http://helephant.com/2008/08/23/javascript-anonymous-functions/
Good luck with your project! Let me know if you have any questions.
Here's some code without the use of timers. Call startCountdown() every time you want to re-initialize the count-down. Call getAvailableScore() when you want to fetch the current available score. You will have to decide what to do when the available score goes to zero.
var beginCountDownTime;
function startCountdown() {
beginCountDownTime = new Date();
}
function getAvailableScore {
var now = new Date();
var delta = (now.getTime() - beginCountDownTime.getTime()) * 1000; // time elapsed in seconds
var points = 100 - (delta / 60);
return(Math.round(Math.max(points, 0))); // return integer result >= 0
}
Maybe something like:
// Ad score
var score = 0;
var pointsAvailable = 100;
//$('#score').text(score);
function foundMatchingBlocks(event, params) {
params.elements.remove();
score += pointsAvailable;
$('#score').text(score);
pointsAvailable = 100;
};
$(document).ready(function() {doTimer();});
function doTimer() {
setTimeout('reducePoints()',1000);
}
function reducePoints() {
if(pointsAvailable>40) {
pointsAvailable--;
}
doTimer();
}

Categories

Resources