I build a countdown timer, so - When I stay in the countdown chrome tab, its work fine.
But when I go to another tab (while the countdown) running, its made it slower.
for example when you start countdown for 30 seconds, and you go to another tab for 10 seconds, when you go back to the countdown tab it will be in 24 seconds - instead of 20 seconds (its just example its very changeable).
stopper = setTimeout(progressCountdown, 1000);
the progressCountdown function its not needed here because its work fine when I in the countdown tab.
There is some option to fix that?
A timeout is not guaranteed to be run after exactly the given delay. In fact, there can be a number of reasons why the delay will be longer than the one you give, for instance if your web page or CPU is overloaded with work, or if it's throttled by the browser to save power and battery (which is what many browsers do with background tabs). MDN has a list with many reasons why these things may occur.
What seems to be your actual problem, however, is that you're using the timeouts to track time itself. This will often be very inaccurate due to the above problems. What you should do instead is to use a clock to keep track of the time spent before the timeout fires:
// performance.now() is a good way of measuring durations
let atStart = performance.now();
function timeoutCallback() {
let now = performance.now();
// milliseconds is accurate now matter how long the timeout actually took
// no matter if in foreground or in background tab
let milliseconds = now - atStart;
// number of seconds left
let secondsLeft = Math.ceil(30 - milliseconds / 1000);
console.log('Countdown: ' + secondsLeft);
if(secondsLeft > 0)
setTimeout(timeoutCallback, 1000);
}
setTimeout(timeoutCallback, 1000);
Related
I was testing my website out with chrome throttling, using the low-end mobile. I noticed one of my JS intervals were running much slower than it should be for no reason. I decided to investigate further, so I wrote this code:
let prev = Date.now();
let x = 0;
setInterval(() => {
if(x++ % 100 == 0) {
console.log(Date.now() - prev);
prev = Date.now();
}
}, 10);
It sets an interval to run every 10ms, and it prints the time elapsed every 100 runs. This should be 1 second, but it is printing 4-8 seconds. This shouldn't happen, because getting the date every 100 runs, and then logging it, should be a piece of cake, even for "low-end mobile".
My best guess right now is that chrome's throttling also specifically throttles the event loop, which sometimes gives misleading results, like here. Is this true?
I have made a countdown timer. It uses the date function to get the current time. Then it stores that time in another var. This new var gets changed hours/minutes/seconds, so the format should be the same as the date function.
Then I turn both variables into time since counting, in milliseconds.
Then I substract the current date from the new date thingy, to get the time difference from both variables in milli seconds. This should be the difference from the current time to the target time.
After this, I will turn the difference into a readable hours/minutes/seconds timeformat, which will be displayed in a div. Also added a piece of code for allowing an blinking countdown timer, which will give 5 minutes extra time if the timer has run out. (this countdown timer should be part of a larger script, doing things)
BIG PROBLEM IS: The timer works. Does everything I want it to do. But it's very laggy! It skips displaying seconds, even if I set the setTimeout to 10 ms. I also use a clock using the same timer set-up (different vars), and that clock doesn't skip any time, with a setTimeout of 1000 ms...
Tryed to make some calculations smaller, even read alot about the setTimeout and setInterval drift in javascript, but this doesn't explain my current problems. (using setTimeout for a chat, to reload messages every 500 ms, and that works like a charm so my computer/client/server can handle smaller then 1000 ms times)
Skipping seconds happens on IE and firefox. Other countdown timers (which don't do what I want them to do) also run fine in my browser. What's the problem here?!?
https://jsfiddle.net/77cnvq82/
function startMyFunction() {
setTimeout(myFunction, 100);
}
In this example, the speed has been set to 100ms
The actual issue is in your rounding and math, not in the display code itself.
If you change your display line to:
timerShowRemaining = timerShowRemaining+timerHours+":"+timerMinutes+":"+timerSeconds
+ (new Date());
It will display the current time and you'll see the seconds count up evenly, even as your calculated numbers jerk and lag.
How to get a better animation, dinamically, even when browser is busy or idle, for different devices which have different hardware capacity.
I have tried many ways and still cannot find the right way to make the game to display a better animation.
This is what i tried:
var now;
var then = Date.now();
var delta;
window.gamedraw = function(){
now = Date.now();
delta = now - then;
if(delta > 18){
then = now - (delta % 18);
game_update();
}
}
window.gameloop = setInterval(window.gamedraw,1);
18 is the interval value to update the game, but when browser is busy this interval is not good, and it needs to lower. How to get a better animation dinamically, even when browser is idle or busy ?
I suppose that the interval value is the problem, because if interval is lower then game animation is very fast, if this value is 18 then game animation is good but not when browser is busy, and I do not have idea how to change it dinamically.
To get a smooth animation, you must :
• Synchronise on the screen.
• Compute the time elapsed within your game.
• Animate only using this game time.
Synchronizing with the screen is done by using requestAnimationFrame (rAF).
When you write :
requestAnimationFrame( myCalbBack ) ;
You are registering myCalbBack to be called once, the next time the screen is available to draw on.
( If you know about double buffering (canvas are always double-buffered), this time is the next time the GPU will swap the draw buffer with the display buffer. )
If, on the other hand, you don't use rAF but a interval/timeout to schedule the draws, what will happen is that the draws won't get actually displayed until next display refresh. Which might happen (on a 60Hz display) any time from right now to 16.6 ms later.
Below with a 20ms interval and a 16 ms screen, you can see that the images actually displayed will be alternatively 16ms away OR 2*16ms away - never 20, for sure-. You just can't know, from the interval callback, when the actual draw will show. Since both rAF and Intervals are not 100% accurate, you can even have a 3 frames delta.
So now that you are on sync with the screen, here's a bad news : the requestAnimationFrame does not tick exactly regularly. For various reasons the elapsed time in between two frames might change of 1ms or 2, or even more. So if you are using a fixed movement each frame, you'll move by the same distance during a different time : the speed is always changing.
(expl : +10 px on each rAF,
16.6 display -->> rAF time of 14, 15, 16 or 17 ms
--> the apparent speed varies from 0.58 to 0.71 px/ms. )
Answer is to measure time... And use it !
Hopefully requestAnimationFrame provides you the current time so you don't even have to use Date.now(). Secondary benefit is that this time will be very accurate on Browsers having an accurate timer (Chrome desktop).
The code below shows how you could know the time elapsed since last frame, and compute an application time :
function animate(time) {
// register to be called again on next frame.
requestAnimationFrame(animate);
// compute time elapsed since last frame.
var dt = time-lastTime;
if (dt<10) return;
if (dt >100) dt=16; // consider only 1 frame elapsed if unfocused.
lastTime=time;
applicationTime+=dt;
//
update(dt);
draw();
}
var lastTime = 0;
var applicationTime = 0;
requestAnimationFrame(animate);
Now last step is to always use time inside all you formulas. So instead of doing
x += xSpeed ;
you'll have to do :
x += dt * xSpeed ;
And now not only the small variations in between frames will be taken into account, but your game will run at the same apparent speed whatever the device's screen (20Hz, 50Hz, 60 Hz, ...).
I did a small demo where you can choose both the sync and if using fixed time, you'll be able to judge of the differences :
http://jsbin.com/wesaremune/1/
You should use requestAnimationFrame instead of setInterval.
Read this article or this one.
The latter one is in fact exactly discussing your approach (with the delta time), and then introduces the animation frame as a more reliable alternative.
The first article is really a great resource. For starters, with your interval of 18 ms you're apparently aiming for something close to 60 fps. This is in fact the default for requestAnimationFrame, so you don't need to write anything special:
function gamedraw() {
requestAnimationFrame(gamedraw); //self-reference
game_update(); //your update logic, propably needs to handle time intervals internally
}
gamedraw(); //this starts the animation
If you want to set the update interval explicitly, you can do so by wrapping the requestAnimationFrame inside a setInterval, like this:
var interval = 18;
function gamedraw() {
setTimeout(function() {
requestAnimationFrame(gamedraw);
game_update(); //must handle time difference internally
}, interval);
}
gamedraw();
Note that the game_update() function must keep track of when it was last called in order to e.g. move everything twice as far as normal in case a frame had to be skipped.
Actually, this means you could (and probably should) refactor your game_update() function to take the time that has actually passed as an argument instead of determining that internally. (There's no functional difference, it just is better, clearer code IMO because it doesn't hide the timing magic.)
var time;
function gamedraw() {
requestAnimationFrame(gamedraw);
var now = new Date().getTime(),
dt = now - (time || now);
time = now; //reset the timer
game_update(dt); //update with explicit and variable time step
}
gamedraw();
(Here I dropped the explicit frames again.)
Still, I urge you to read the first article because it also deals with cross-browser issues that I haven't gotten into here.
You should use requestAnimationFrame. It will queue up a callback to run on the next time the browser renders a frame. To achieve constant updating, call the update function recursively.
var update = function(){
//Do stuff
requestAnimationFrame(update)
}
What's the best way to create a timer in JS?
I've been using this so far:
var sec = 0;
setInterval(function (){sec +=1}, 1000);
I've noticed that, when I need miliseconds, it slows down by a lot. On browser tab changes, it completely stops.
var milisec = 0;
setInterval(function (){milisec +=1}, 1);
I'm looking for a better way to handle this, which will also continue to work when the browser window is changed.
With milliseconds, the resolution of the timer isn't large enough. In most cases the callback won't be called more often than roughly 50 to 250 times per second, even when you set the interval to 1ms. See Timer resolution in browsers (as referred to by Sani Huttunen) for an explanation.
With 1000ms it will work better. But still the timer won't be fired when the tab is inactive, and may be delayed when the cpu is busy or another script is running on your page.
One solution is to not increment a counter, but to test how much time has actually passed since the previous call of the timer. That way, the timing remains accurate, even when the intervals have been delayed or paused inbetween.
This snippet will remember the start date, and on each timer interval, update seconds and milliseconds to the difference between the current time and the start time.
var start = new Date();
var milliseconds = 0;
var seconds = 0;
setInterval(function()
{
var now = new Date();
milliseconds = now.getTime() - start.getTime();
seconds = round(milliseconds / 1000);
}, 1000);
I've set the interval to 1000 again. You might set it shorter, but it will cost more performance.
Related question: How can I make setInterval also work when a tab is inactive in Chrome?
Based on #goleztrol's solution, I've created an alternate solution specifically for my situation (it might not work for everyone).
I just ask the exact time when it's needed with this function, to know the exact miliseconds passed:
var start = new Date();
var msPassed = function() {
var now = new Date();
var ms = now.getTime() - start.getTime();
return ms
}
msPassed(); //returns time passed in ms
I needed to position objects (on creation) depending on how much time passed until their creation, so for my case this is a perfect solution. However, my initial question asks for the perfect timer, and this is not it. Anyway, here it is for future reference.
I build a web app and I use setInterval with 500ms timer for some clock.
When the window is active the clock runs perfect, I use that:
var tempTimer = 0;
setInterval(function () {
goTimer()
}, 500);
function goTimer() {
tempTimer++;
$("#timer").val(tempTimer);
}
But the problem is when the window/tab becomes inactive - the interval is changed to 1000ms!
When i focus the window/tab again, it changes back to 500ms.
check this out: http://jsfiddle.net/4Jw37/
Thanks a bunch.
Yes, this behavior is intentional.
See the MDN article:
In (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2) and Chrome 11,
timeouts are clamped to firing no more often than once per second
(1000ms) in inactive tabs; see bug 633421 for more information about
this in Mozilla or crbug.com/66078 for details about this in Chrome.
And the spec says:
Note: This API does not guarantee that timers will run exactly on
schedule. Delays due to CPU load, other tasks, etc, are to be
expected.
You seem to want a timer that increments every half second. You can do that much more accurately by keeping track of the total time since you started and doing some math.
var tempTimer = 0;
var startedTimer = Date.now();
setInterval(goTimer, 250); // a little more often in case of drift
function goTimer() {
tempTimer = Math.floor((Date.now() - startedTimer) / 500);
$("#timer").val(tempTimer);
}
See this work here: http://jsfiddle.net/4Jw37/2/
So this does update every half second, but it doesn't need to. If it skips a few beats it will fix itself the next time it fires because it's recalculating each time based on the time since it started tracking. The number of times the function runs is now has no effect on the value of the counter.
So put in another way, do not ask how many times you incremented the count. Instead ask how many half second segments have passed since you started.
For time interval, Browsers may not behave similar for both active and inactive window.
What you can do, When you are setting the time interval you can save the timestamp(initial time). On window.onfocus, take on there timestamp (now) find the difference between initial time and now and use that update the tempTimer.