Javascript Loop: run function after precise time - javascript

I have searched high and low for a solution to this (that I can understand) and have yet to find one.
Fiddle here
https://jsfiddle.net/the_o/a7dwp41s/
The Goal:
To have the text change after a set period of time (for example 1 second).
The Problem:
The timing is not accurate at all. It's not apparent in the fiddle but on my page the text sometimes changes waaaay too fast. Also sometimes the loop will just stop. I know that setTimeout is not accurate from reading other Stack Overflow answers, but have not come across a good solution for running a function after a set period accurately. I'd appreciate some help.
The HTML:
<span class="text-center"><span class="top-line">A heading here</span>
<br><span class="bottom-line">There once was a...<br>
<span id="changeTextMobile"></span></span>
</span>
The Javascript:
var text = ["carrot", "potato", "tomato", "lettuce", "radish", "cabbage", "melon", "cucumber"];
var elem = document.getElementById("changeTextMobile");
var counter = 0;
function rotate_text() {
elem.innerHTML = text[counter];
if (counter < 8) {
counter++window.setTimeout(rotate_text, 1200);
}
if (counter == 8) {
counter = 0;
}
}
rotate_text();
Here's the fiddle again: https://jsfiddle.net/the_o/a7dwp41s/

The classic approach here is to set a fine-grained timer (either with setTimeout or more likely requestAnimationFrame, then within that examine the current time and compute the difference with the previous time, and execute your action if the desired amount of time has passed. RAF will send your callback a high-resolution timer which can be used if you are interested in sub-millisecond precision.
var text = ["carrot", "potato", ...];
var elem = document.getElementById("changeTextMobile");
var counter = 0;
var DELAY = 1000;
var old_timestamp = 0;
function rotate_text(timestamp) {
if (timestamp > old_timestamp + DELAY) { // HAVE WE WAITED LONG ENOUGH?
update_vegetable(); // CHANGE VEGETABLE NAME.
old_timestamp = timestamp; // REMEMBER WHEN WE DID THAT.
}
requestAnimationFrame(rotate_text); // RINSE AND REPEAT
}
rotate_text(0);
where update_vegetable would look something like
function update_vegetable() {
elem.textContent = text[counter++ % 8];
}
This should give you very accurate results. However, note that some browsers may slow down requestAnimationFrame when the tab is in the background. Also note that requestAnimationFrame may require vendor prefixing in certain browsers.
If you don't want to use requestAnimationFrame for some reason, you should be able to replace the call to it with setTimeout(tick, 16) with similar results.
Analysis of current code
In your current code, when counter reaches 8 and you reset it to 0, you are not calling setTimeout again to continue the sequence. That seems wrong.
if (counter == 8) {
counter = 0; //YOU ARE NOT RESETTING THE TIMEOUT.
}
In any case, you're better off using counter % 8 as another answer suggests and as shown above.
Also, the line below seems broken and is missing a semicolon. Is this your actual code? What it would do is add to counter the timer ID returned by setTimeout, which is completely meaningless.
counter++window.setTimeout(rotate_text, 1200);
should be
counter++;
window.setTimeout(rotate_text, 1200);

You should use setInterval for such cases. I have updated JSFiddle.
setTimeout
setTimeout is used for cases when you have to add a delay before a function is executed.
setInterval
setInterval is used for case where you have to run certain function after certain time delay.
Also, just a note, setTimeout(function(){},0) does not mean it will be executed immediately. When you use these function, an event is registered to be executed at certain tick, so setTimeout(function(){},0) will be triggered on next tick and not immediately.
var text = ["carrot", "potato", "tomato", "lettuce", "radish", "cabbage", "melon", "cucumber"];
var counter = 0;
function initInterval() {
interval = setInterval(function() {
counter++;
$("#changeTextMobile").text(text[counter % 8]);
}, 1200);
}
$(document).ready(function() {
initInterval();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span class="text-center"><span class="top-line">A heading here</span>
<br><span class="bottom-line">There once was a...<br>
<span id="changeTextMobile"></span></span>
</span>
Hope it helps.

Another way to accomplish your goal would be to use the setInterval() method instead of the setTimeout() method.
Documentation for the method is found on W3Schools.com
Unless I am misunderstanding your intention, you don't just want to change your text once, but repeatedly. In this case, setInterval() is the choice as setTimeout() is intended to only run a function once. This could be the source of your consistency issues.
If and when you wish to stop the timer's repeat functionality, use clearInterval() in your trigger.

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.

Is there a better solution than setInterval when I need the interval to be very precise over time?

I'm building a browser game similar to Guitar Hero. The way it works is setInterval is set to execute every 16th note depending on the tempo, which is usually 100-150 milliseconds. The function it executes just checks if there's a note that needs to be checked against the user's keypresses.
But I've been reading about how setInterval can suffer from drift and may not be very accurate. For a song that lasts 3-4 minutes and needs 100ms precision (if the timing mechanics are even slightly off, that might be very frustrating for the users), it seems like it may not be the greatest solution.
So is there an alternative that's more precise? T
It probably would be a better idea to calculate everything in absolute time. So have a var starttime = Date.now();, and then calculate where every note should be var expected_note_time = starttime+beat_interval*beat_number. You can then add a listener on keypress and then log the exact time the keypress was hit. If abs(time_pressed-expected_note_time) < ALLOWED_MISTAKE_VARIANCE, then add that point to the user's score. Good luck.
Agree, setInterval have some issues, like it doesn't care whether the callback is still running or not and isn't that flexible.
you can implement you own method something like this :
function interval(func, wait, times){
var interv = function(w, t){
return function(){
if(typeof t === "undefined" || t-- > 0){
setTimeout(interv, w);
try{
func.call(null);
}
catch(e){
t = 0;
throw e.toString();
}
}
};
}(wait, times);
setTimeout(interv, wait);
};
this function has an internal function called interv which gets invoked automatically via setTimeout, within interv is a closure that checks the the repetition times, invokes the callback and invokes interv again via setTimeout. In case an exception bubbles up from the callback call the interval calling will stop and the exception will be thrown.
you can use it like this :
interval(function(){
// Code block goes here
}, 1000, 10);
which execute a piece of code 5 times with an interval or 10 seconds and you can do something in between.
You could cache the ticks at the start of the song, get the number of ticks since then, see if a note exists at that point.
Return the number of milliseconds since 1970/01/01:
var d = new Date();
var n = d.getTime();
The result of n could be:
1502156888172
From: https://www.w3schools.com/jsref/jsref_gettime.asp
and needs 100ms precision
The setInterval() function will drift because of the way javascript is built (event loop) and if you block it with a heavy CPU intensive task, it will delay. However, the drift will be very small (less that a ms) if you do it correctly.
A good way to avoid that would be to use multiple thread, but that is not easily achieved in JavaScript.

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.

Trying to display multiple count down timers on same page

I am trying to display several count down timers on same page. now as far as i know there are 2 ways of doing it without using jquery plugins or some other scripts (if you know of a good one please let me know)
starting 1 sec setInterval and a global variable that will contain milliseconds and then just reduce -1000 every interval.
creating a function that reduce 1 sec from a global variable and then at the bottom of that function setting a setTimeout of 1 sec that will run that functions so basically recursion every 1 sec.
My question is which of the 2 options will work better and/or faster?
here is demonstrative code for both:
setInterval:
var amount_of_seconds_left = 46800000;
setInterval(function(){
if(amount_of_seconds_left > 1000){
amount_of_seconds_left -= 1000;
}
},1000);
setTimeout:
var amount_of_seconds_left = 46800000;
function startTime(){
if(amount_of_seconds_left > 1000){
amount_of_seconds_left -= 1000;
t=setTimeout(function(){startTime()},1000);
}
}
Both ways could work but i was wondering performance wise which is better and is performance is even an issue with this ?
setInterval and setTimeout don't start after 1000ms e.g. if another script is running, so both can cause delays. It would be better to use the setIntervall to call the display update only and use the the Date object to calculate the exactly remaining time. E.g. after the browser was busy the timer shows the correct time after the next update.
Here an example:
HTML:
<div id="timer1"></div>
<div id="timer2"></div>
javascript:
// update all timer
function updateTimer() {
for (var i in aTimer) {
var oTimer = document.getElementById(aTimer[i].sId);
var iSeconds = parseInt((aTimer[i].iFinished - Date.now()) / 1000);
oTimer.innerHTML = iSeconds;
}
}
// Init all timers with DOM-id and finish time
var aTimer = [
{ sId: 'timer1', iFinished: Date.now() + 46800000 },
{ sId: 'timer2', iFinished: Date.now() + 780000}
];
// call display update
setInterval(function() {
updateTimer();
}, 333);
I belive that the setInterval code executes every 1000ms exactly, while the setTimeout waits 1000ms, runs the function, which takes some ms, then sets another timeout. So the wait period is actually greater than 1000ms.
From this post:
setTimeout or setInterval?

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

Categories

Resources