Trying to build a very simple Javascript countdown. However, whenever the tab is inactive, the countdown begins to lag and holds an incorrect count.
See jsfiddle here for example: /
function initTimer(t) {
var self = this,
timerEl = document.querySelector('.timer'),
minutesGroupEl = timerEl.querySelector('.minutes-group'),
secondsGroupEl = timerEl.querySelector('.seconds-group'),
minutesGroup = {
firstNum: minutesGroupEl.querySelector('.first'),
secondNum: minutesGroupEl.querySelector('.second')
},
secondsGroup = {
firstNum: secondsGroupEl.querySelector('.first'),
secondNum: secondsGroupEl.querySelector('.second')
};
var time = {
min: t.split(':')[0],
sec: t.split(':')[1]
};
var timeNumbers;
function updateTimer() {
var timestr;
var date = new Date();
date.setHours(0);
date.setMinutes(time.min);
date.setSeconds(time.sec);
var newDate = new Date(date.valueOf() - 1000);
var temp = newDate.toTimeString().split(" ");
var tempsplit = temp[0].split(':');
time.min = tempsplit[1];
time.sec = tempsplit[2];
timestr = time.min + time.sec;
timeNumbers = timestr.split('');
updateTimerDisplay(timeNumbers);
if (timestr === '0000')
countdownFinished();
if (timestr != '0000')
setTimeout(updateTimer, 1000);
}
function animateNum(group, arrayValue) {
TweenMax.killTweensOf(group.querySelector('.number-grp-wrp'));
TweenMax.to(group.querySelector('.number-grp-wrp'), 1, {
y: -group.querySelector('.num-' + arrayValue).offsetTop
});
}
setTimeout(updateTimer, 1000);
}
I'm unsure whether the problem lies with the animation, or with the JS code itself.
For clarification: I want the countdown to continue when the tab is inactive, or to 'catch up with itself' when the tab es back in to focus.
I know that setTimeout
and setInterval
can cause issues with inactive tabs, but I'm not entirely sure how to fix this.
Any help would be much appreciated!
Trying to build a very simple Javascript countdown. However, whenever the tab is inactive, the countdown begins to lag and holds an incorrect count.
See jsfiddle here for example: https://jsfiddle/gbx4ftcn/
function initTimer(t) {
var self = this,
timerEl = document.querySelector('.timer'),
minutesGroupEl = timerEl.querySelector('.minutes-group'),
secondsGroupEl = timerEl.querySelector('.seconds-group'),
minutesGroup = {
firstNum: minutesGroupEl.querySelector('.first'),
secondNum: minutesGroupEl.querySelector('.second')
},
secondsGroup = {
firstNum: secondsGroupEl.querySelector('.first'),
secondNum: secondsGroupEl.querySelector('.second')
};
var time = {
min: t.split(':')[0],
sec: t.split(':')[1]
};
var timeNumbers;
function updateTimer() {
var timestr;
var date = new Date();
date.setHours(0);
date.setMinutes(time.min);
date.setSeconds(time.sec);
var newDate = new Date(date.valueOf() - 1000);
var temp = newDate.toTimeString().split(" ");
var tempsplit = temp[0].split(':');
time.min = tempsplit[1];
time.sec = tempsplit[2];
timestr = time.min + time.sec;
timeNumbers = timestr.split('');
updateTimerDisplay(timeNumbers);
if (timestr === '0000')
countdownFinished();
if (timestr != '0000')
setTimeout(updateTimer, 1000);
}
function animateNum(group, arrayValue) {
TweenMax.killTweensOf(group.querySelector('.number-grp-wrp'));
TweenMax.to(group.querySelector('.number-grp-wrp'), 1, {
y: -group.querySelector('.num-' + arrayValue).offsetTop
});
}
setTimeout(updateTimer, 1000);
}
I'm unsure whether the problem lies with the animation, or with the JS code itself.
For clarification: I want the countdown to continue when the tab is inactive, or to 'catch up with itself' when the tab es back in to focus.
I know that setTimeout
and setInterval
can cause issues with inactive tabs, but I'm not entirely sure how to fix this.
Any help would be much appreciated!
Share Improve this question edited Oct 1, 2016 at 14:52 Adam Ayrton Stoner asked Oct 1, 2016 at 14:48 Adam Ayrton StonerAdam Ayrton Stoner 931 silver badge5 bronze badges 9- please remove any code not relevant to the specific problem – charlietfl Commented Oct 1, 2016 at 14:51
- Possible duplicate of Why does JavaScript setTimeout lag when in another tab? – alexwc_ Commented Oct 1, 2016 at 14:55
- 2 hint: store end time as date and always calculate based on difference between now and end – charlietfl Commented Oct 1, 2016 at 14:55
- @alexwc_ that link is relevant but doesn't provide solution for this situation so it's not really a duplicate – charlietfl Commented Oct 1, 2016 at 14:58
- Use the Task scheduler if you can use service worker – Endless Commented Oct 1, 2016 at 15:09
4 Answers
Reset to default 6For this you can use the HTML5 Visibility API for detecting if the browser tab is active or not. And use regular binding of event handlers for focus and blur for the browser window.
Basically you pause()
the timeline when you blur out of the tab, and then play()
when you give the tab refocus. Example of this in action:
http://codepen.io/jonathan/pen/sxgJl
// Set the name of the hidden property and the change event for visibility
var hidden, visibilityChange;
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
// If the page is hidden, pause the video;
// if the page is shown, play the video
function handleVisibilityChange() {
if (document[hidden]) {
tl.pause();
} else {
tl.play();
}
}
// Warn if the browser doesn't support addEventListener or the Page Visibility API
if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
// do nothing or throw error via alert()
alert("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
} else {
// Handle page visibility change
// Pause timeline
tl.pause();
}
HTML5 Visibility Docs:
https://developer.mozilla/en-US/docs/Web/API/Page_Visibility_API
Regarding GreenSock Forum Topic:
http://forums.greensock./topic/9059-cross-browser-to-detect-tab-or-window-is-active-so-animations-stay-in-sync-using-html5-visibility-api/
Also in GSAP the equivalent for setTimeout() is delayedCall()
Provides a simple way to call a function after a set amount of time (or frames). You can optionally pass any number of parameters to the function too.
GSAP delayedCall(): http://greensock./docs/#/HTML5/GSAP/TweenMax/delayedCall/
//calls myFunction after 1 second and passes 2 parameters:
TweenMax.delayedCall(1, myFunction, ["param1", "param2"]);
function myFunction(param1, param2) {
//do stuff
}
I hope this helps!
before all, i have to mention that, those countdown animation is truly outstanding and elegant dear sir! well done...
Now to the answers: as mentioned in many articles, and here is one of them:
setInterval(func, delay) does not guarantee a given delay between executions. There are cases when the real delay is more or less than given. In fact, it doesn’t guarantee that there be any delay at all.
Source: http://javascript.info/tutorial/settimeout-setinterval
and..
Most browsers apply performance improvements by reducing the priority of several tasks on inactive tabs or even some portions of the page that aren't on screen. Sometimes these improvements affects the executions of Javascript intervals as well.
Source: How can I make setInterval also work when a tab is inactive in Chrome?
as you can see, they mentioned that setInterval
does not guarantee a given delay between executions even when the tab is active, and lets just assume that setTimeout
(the one that you used) is also the same because they are relatively is "the same"
so what is the solution to this problem?
well, what you actually can do is check how many times has actually elapsed between the event, something like this Codepen
EDIT: as you have requested, here is a fiddle of your countdown with the fix, and i have //mented
on the changes i made to the fiddle, so it would hopefully make it easier for you to understand. So now, when you paired the countdown to other timer (ex. your phone's timer) you should get the same result even when the tab is inactive or the frame rate slowdown.
if you have enjoyed my answer please consider mark it as "the answer" or at least up vote it ;)
The simplest way to ensure the timer stays correct when a user moves off the tab and then returns is to use session storage - to store the original starting time:
on the first load - get the local current datetime and store into session storage (note that will only accept a string - so you will have to stringify the value and then parse it out again upon retrieval).
when the tab loses focus that set start time will still be stored as startTime in ss. When the tab regains focus - have a function that gets the new current datetime, gets the stored datedtime from session storage and calculates the difference. Then the timer can be updated to the new reduced time.
Eg if its 10:00 am on page load - set the startTime into ss. Then the user spends 1 minute on the site and goes offsite for 5 minutes. Upon returning - the above described process will determine the current time is 10:06 and determine that its 6 minutes later than the start time so can update the timer as such.
This avoids the need for intervals and timers etc. You can take the specificity down to ms if needed. And also - I like your fiddle too.
You can use this code. Each time the tab changes, it calculates the end time again and updates the counter.
let interval;
let duration=timeDifference(endTime(),nowDate())
updateTime();
$(window).focus(function () {
clearInterval(interval)
updateTime();
});
function updateTime() {
interval = setInterval(function () {
var timer = duration.split(':');
//by parsing integer, I avoid all extra string processing
var hours = parseInt(timer[0], 10);
var minutes = parseInt(timer[1], 10);
var seconds = parseInt(timer[2], 10);
--seconds;
minutes = (seconds < 0) ? --minutes : minutes;
if (minutes < 0) clearInterval(interval);
seconds = (seconds < 0) ? 59 : seconds;
seconds = (seconds < 10) ? '0' + seconds : seconds;
hours = (hours < 10) ? '0' + hours : hours;
//minutes = (minutes < 10) ? minutes : minutes;
$('.countdown').html(hours + ':' + minutes + ':' + seconds);
duration = hours + ':' + minutes + ':' + seconds;
}, 1000);
}
function nowDate() {
let date = new Date()
let time1 = new Date();
date = date.toISOString().slice(0, 10);
date = date.split('-');
return new Date(date[2] + '/' + date[1] + '/' + date[0] + " " + time1.getHours() + ":" + time1.getMinutes() + ":" + time1.getSeconds());
}
function endTime() {
let endTime = $('input[name=end_date]').val();
endTime = endTime.split(' ');
let date = endTime[0].split('-');
let time = endTime[1];
return new Date(date[2] + '/' + date[1] + '/' + date[0] + " " + time);
}
function timeDifference(date1,date2) {
var difference = date1.getTime() - date2.getTime();
var daysDifference = Math.floor(difference/1000/60/60/24);
difference -= daysDifference*1000*60*60*24
var hoursDifference = Math.floor(difference/1000/60/60);
difference -= hoursDifference*1000*60*60
var minutesDifference = Math.floor(difference/1000/60);
difference -= minutesDifference*1000*60
var secondsDifference = Math.floor(difference/1000);
return hoursDifference +":"+minutesDifference+":"+secondsDifference
}