jQuery/JavaScript: My recursive setTimeout function speeds up when tab becomes inactive - javascript

I've got an odd little dilemma in this jQuery slideshow plugin that I am building.
It's nothing fancy and the code I have written to date is working great however I have noticed that when I leave the site running and switch to a new tab and continue browsing the web in this other tab (Chrome for Mac in my case) that when I return to my site, the setTimeout call seems to have speed up and instead of waiting for the timer to finish the fire the event, it fires continuously.
Here is my (simplified) code:
var timer;
var counter;
var slides; // collection of all targeted slides.
// animate to the next slide
function nextSlide() {
// stop timer
methods.stopTimer();
// increase counter
counter++;
if ( counter > slides.length-1 ) { counter = 0; } // if counter is greater than the amount of slides, back to the start.
// inner = container to be animated
// in the complete callback restart the timer.
inner.animate({
'left': '-'+slides.eq( counter ).position().left
}, {
duration : settings.animationSpeed,
easing : 'easeInOutExpo',
complete : startTimer()
});
}
// timer functions.
function startTimer() {
if ( timer === '' ) {
timer = setTimeout( function() {
nextSlide();
} , 3000 );
}
}
function stopTimer() {
clearTimeout( timer );
timer = '';
}
So what should happen is that at the end of the animation, the timer gets reattached with another setTimeout call so that it becomes a continuous slideshow (and this works just fine until you leave the tab.
Once you leave the tab and return to the tab with slideshow it seems that the 3000 ms timer has been reduced to invoke instantly and now the moment the animation finishes the next one starts with no delay at all.
Any ideas on why this is happening on how it can be solved would be much appreciated.
Thanks for reading,
Jannis

Some browsers (like Chrome) drastically slow down recurring timers when the tab goes inactive and then, when the tab comes active again, they try to "catch up" so that the same number of actual timer events has occurred. All I can think of as a work-around is for you to stop the slideshow entirely when the tab goes inactive and start it again when it comes active.

Instead of timeouts have you tried intervals? Also for it to be recursive, just call the nextSlide() function as its own callback:
var counter = 1;
// animate to the next slide
function nextSlide() {
// increase counter
counter++;
// if counter is greater than the amount of slides, back to the start.
counter = ( counter > slides.length-1 ) ? 0 : counter;
// inner = container to be animated
// in the complete callback restart the timer.
inner.animate(
{
'left': '-' + slides.eq( counter ).position().left
},
{
duration : settings.animationSpeed,
easing : 'easeInOutExpo',
complete : nextSlide()
});
}
Then it's just a matter of starting and stopping an interval:
var slideshow;
function startSlideshow()
{
slideshow = setInterval(nextSlide(),3000);
}
function stopSlideshow()
{
clearInterval(slideshow);
inner.stop();
}

Related

Reset timer on mousemove and keypress

The below javascript code auto refresh the page every 10 sec.
My question is how can I reset the timer on mousemove and keypress.
Java-Script
<script type="text/javascript" language="JavaScript">
var secs;
var timerID = null;
var timerRunning = false;
var delay = 1000;
function InitializeTimer(seconds) {
//length of the timer, in seconds
secs = seconds;
StopTheClock();
StartTheTimer();
}
function StopTheClock() {
if (timerRunning)
clearTimeout(timerID);
timerRunning = false;
}
function StartTheTimer() {
if (secs == 0) {
StopTheClock();
window.location.href = window.location.href;
}
else {
document.getElementById("lbltime").innerText = secs + " ";
secs = secs - 1;
timerRunning = true;
timerID = self.setTimeout("StartTheTimer()", delay);
}
}
</script>
Thank you in advance.
If I understood you correctly, something like this (really basic example: the timer is reset on mousemove and keypress):
var elem = document.getElementById("timer"), timeout, startTimer = function timer() {
elem.textContent++;
timeout = setTimeout(timer, 1000)
}
function resetTimer() {
// here you reset the timer...
clearTimeout(timeout);
elem.textContent = -1;
startTimer();
//... and also you could start again some other action
}
document.addEventListener("mousemove", resetTimer);
document.addEventListener("keypress", resetTimer);
startTimer();
<span id="timer">-1</span> - Move cursor or press a key to reset timer.
As I guess, you want to delay page reloading if there was some user activity on the page. In your reload() function you just need to check, if timer's value reached the limit (like 60 seconds). If yes, execute reloading. However, notice, that handling mousemove event can make browser's tab have high CPU usage, if, for example, I just start to move my cursor quickly. You might want to pick some important areas and set handlers on the corresponding elements. For example, keypress event can be listened to only on a comment form or mousemove can be listened to only on a menu or on an article.
PS. Also take into account that
The keypress event is fired when a key is pressed down, and that key normally produces a character value
so, if user presses, for example, ctrl or alt the event will not be fired.
Your code seems a bit too branched out in my opinion, what I would do is simplify it a bit.
window.refreshTimeout = null;
function refreshPage(){
location.reload();
}
function refresh(){
clearTimeout(window.refreshTiemout);
window.refreshTimeout = setTimeout(refreshPage, 2000)
}
window.refreshTimeout = setTimeout(refreshPage, 2000)
window.addEventListener('mousemove', refresh)
<h4>HelloWorld</h4>
As you can see, you attach an event listener to the window for a mouse move (you can attach others too) within which you cancel the previous timeout and start a new one. Initially you of course start a timeout.

Only setTimeout executes function. JS

I am trying to implement kind of player on my website.
If press 'Play' button, the music starts and the page smoothly scrolls down.
But when you press 'Mute' button (function(){music.volume=0}) I am not sure why the page appears at the top again. window.scroll() doesn't do anything without delay. So i am using setTimeout function to scroll the page on the current place. The problem is that in Opera and IE setTimeout takes about 10 ms, so when i click 'Mute' button i see like ticks to top and back. In chrome it takes only 2 ms and there is no problems.
Now when i decide to create my own timeout function the window.scroll() does not work again.
Here is my code:
var isMuted = false;
muteButton.onclick = function() { ////This function works with big delay.
if (!isMuted) {
mainAudio.volume = 0;
isMuted = true;
} else {
mainAudio.volume = bgAudioTrackVolume;
isMuted = false;
}
setTimeout(function() {
window.scroll(0, offset); /// Works
}, 0)
};
Change setTimeout with:
i = 9542155.873; /// I have tried delay time from 1ms - 250ms by changing this value.
while (i > 0.00001) {
i = i / 1.0001234567;
if (i < 0.00001) {
window.scroll(0, offset); /// Does not do anything. Strange! Have tried to change variable with a number.
}
}
Every time i check offset value, it is always available before calling scroll function.
I know that my problem is not usual and i am realy need your help.
The reason that the page scrolls to the top is that you are using a link with the empty bookmark #, which represents the top of the page. The reason that the scroll method doesn't work without a timeout is that jumping to the bookmark happens after the event handler.
Instead of trying to scroll the page back to where it was, just stop the default action of the link by returning false from the event handler:
var isMuted = false;
muteButton.onclick = function() {
if (!isMuted) {
mainAudio.volume = 0;
isMuted = true;
} else {
mainAudio.volume = bgAudioTrackVolume;
isMuted = false;
}
return false;
};
Alternatively, use some other element than a link.

How can I make a timer start & work only when onFocus is true?

I have this timer (see Code below). How can I make sure that the countdown starts AND keeps counting down only if the browser window is onFocus?
(I want it to pause if the user opens another window, for example.)
//Countdown
function timerCountdown()
{
x=100;
document.getElementById('timer1').value=x;
x=x-1;
t=setTimeout("timerCountdown()",1000);
if (c<-1)
{
document.getElementById('timer1').value='Go';
clearTimeout(t);
}
}
window.onblur = function() {
// Stop counting
}
window.onfocus = function() {
// Start counting
}

Other way to "wait" during javascript animation

i'm looking for another way to execute this code :
$.each($("#gallery > img"), function(index,curImg) {
setTimeout(function() {
clearCanvas();
cvsCtx.drawImage(curImg,0,0);
} , index*animationMs);
});
This code draw an image from my gallery to my canvas every animationMs .
But i would like to make it possible to stop the animation, with a "Play/stop" button, I can't do it this way...
Any idea or workaround ?? thank you !!
I can't test it. But you can stop animation by using a variable to hold the setTimeout function as following:
var x; // public var
....
x = setTimeout(......);
// To stop it use:
clearTimeout(x);
Hope this works for you
I find that creating timeouts in a loop is usually too hard to manage - you don't want to have to cancel multiple timeouts. Better to have the function doing the work call itself (indirectly) by setting a timeout just before it completes, because then you can put in a simple if test to decide whether to set the next timeout and continue your animation.
Perhaps a little something like this:
<input id="playPause" type="button" value="Play">
<script>
function initAnimation(animationMs, autoRepeat, waitForPlayButton) {
var currentFrame = 0,
$imgList = $("#gallery > img"),
paused = waitForPlayButton;
function drawNext() {
clearCanvas();
cvsCtx.drawImage($imgList[currentFrame++],0,0);
if (currentFrame >= $imgList.length) {
currentFrame = 0;
if (!autoRepeat) {
paused = true;
$("playPause").prop("value", "Play");
}
}
if (!paused)
setTimeout(drawNext, animationMs);
}
$("playPause").prop("value", waitForPlayButton ? "Play" : "Pause")
.click(function() {
this.value = (paused = !paused) ? "Play" : "Pause";
if (!paused)
drawNext();
});
if (!waitForPlayButton)
drawNext();
}
initAnimation(100, true, false);
</script>
If autoRepeat param is false the animation will run once and stop, but can be restarted via the button, otherwise (obviously) it just keeps repeating.
If waitForPlayButton is false the animation will start immediately, otherwise (obviously) it will start when the button is pressed.
Pressing the button will pause at the current frame.
(Not tested since I don't have a bunch of images handy, but I'm sure you get the idea and can fix any problems yourself. Or let me know if you get errors...)
var images = $("#gallery > img").clone(), interval;
function startLoop() {
interval = setInterval(function(){
var image = images[0];
clearCanvas();
cvsCtx.drawImage(image,0,0);
images.append(image);
}, animationMs);
}
$(".stop").click(function() {clearInterval(interval);});
$(".start").click(startLoop);
setTimeout return a timeoutID which can be given to clearTimeout as a parameter to stop the timeout from happening.
You can read more about this at: https://developer.mozilla.org/en/DOM/window.setTimeout
Good luck
It's not really an animation... but still:
$("#gallery > img").each(function(index,curImg) {
$(this).delay(index*animationMs).queue(function(next) {
clearCanvas();
cvsCtx.drawImage(curImg,0,0);
if (next) next();
});
});
Using jQuery queues like I did allows you to do .stop(true), on $("#gallery > img") or a single image and stop their "animation".
First you could add images to a javascript array variable (eventually global) and then call a function cycle() on that array for all its length.
You should put your setTimeout() call inside that function, assigning it to a variable: var t=setTimeout("cycle()",animationMs); and execute clearTimeout(t); when you want to stop the animation.
Of course you could also save in a variable the frame where you were when stopping the animation and restart exactly from that frame when pressing "play" button.

Run setTimeout only when tab is active

Is there a way to stop setTimeout("myfunction()",10000); from counting up when the page isn't active. For instance,
A user arrives at a "some page" and stays there for 2000ms
User goes to another tab, leaves "some page" open.
myfunction() doesn't fire until they've come back for another 8000ms.
(function() {
var time = 10000,
delta = 100,
tid;
tid = setInterval(function() {
if ( document.hidden ) { return; }
time -= delta;
if ( time <= 0 ) {
clearInterval(tid);
myFunction(); // time passed - do your work
}
}, delta);
})();
Live demo: https://jsbin.com/xaxodaw/quiet
Changelog:
June 9, 2019: I’ve switched to using document.hidden to detect when the page is not visible.
Great answer by Šime Vidas, it helped me with my own coding. For completeness sake I made an example for if you want to use setTimeout instead of setInterval:
(function() {
function myFunction() {
if(window.blurred) {
setTimeout(myFunction, 100);
return;
}
// What you normally want to happen
setTimeout(myFunction, 10000);
};
setTimeout(myFunction, 10000);
window.onblur = function() {window.blurred = true;};
window.onfocus = function() {window.blurred = false;};
})();
You'll see that the window blurred check has a shorter time set than normal, so you can set this depending on how soon you require the rest of the function to be run when the window regains focus.
You can do something like:
$([window, document]).blur(function() {
// Clear timeout here
}).focus(function() {
// start timeout back up here
});
Window is for IE, document is for the rest of the browser world.
I use almost the same approach as Šime Vidas in my slider
but my code is based on document.visibilityState for page visibility checking:
document.addEventListener("visibilitychange", () => {
if ( document.visibilityState === "visible" ) {
slideshow.play();
} else {
slideshow.pause();
}
});
About Page Visibility
API: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
What you'd have to do is set up a mechanism to set timeouts at small intervals, keeping track of total elapsed time. You'd also track "mouseenter" and "mouseleave" on the whole page (the <body> or something). When the short-term timeouts expire, they can check the window state (in or out) and not restart the process when the window is not in focus. The "mouseenter" handler would start all paused timers.
edit — #Šime Vidas has posted an excellent example.
I've finally implemented a variation of #Šime Vidas' answer, because the interval was still running if I opened another program and the browser window was not visible, but the page executing the interval was the active browser tab. So, I've modified the condition to document.hidden || !document.hasFocus(). This way, if the document is hidden or the document doesn't have the focus, the interval function just returns.
(function() {
var time = 10000,
delta = 100,
tid;
tid = setInterval(function() {
if ( document.hidden || !document.hasFocus() ) { return; }
time -= delta;
if ( time <= 0 ) {
clearInterval(tid);
myFunction(); // time passed - do your work
}
}, delta);
})();

Categories

Resources