what speed up "setInterval" when you switch page? - javascript

Here is My page,It is a demo to test moving a element,but after you change your page sometime,and back to here,why the DIV move faster?
My css:
#box1 {
width: 900px;
height: 50px;
background-color: #000;
position: relative;
}
#box2 {
width: 50px;
height: 50px;
background-color: #a00;
position: absolute;
}
My HTML:
<div id="box1">
<div id="box2"></div>
</div>
My Js:
var box2 = document.getElementById("box2");
var remove = setInterval(function () {
box2.style.left = "0px";
var move = setInterval(function () {
var newLeft = Math.min(parseInt(box2.style.left) + 5, 850) + "px";
box2.style.left = newLeft;
if (newLeft == "850px") clearInterval(move)
}, 20);
}, 5000)

The rate of setInterval cannot be trusted. A browser might not fire it when the tab is not focused, and as well might fire it more often than needed.
The behaviour is standardisized in the current HTML5 draft on the WindowTimers interface (which does not mean it was implemented like that). There you will find the 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.
and, even more explicit:
9) Optionally, wait a further user-agent defined length of time.
Note: This is intended to allow user agents to pad timeouts as needed to optimise the power usage of the device. For example, some processors have a low-power mode where the granularity of timers is reduced; on such platforms, user agents can slow timers down to fit this schedule instead of requiring the processor to use the more accurate mode with its associated higher power usage.
You might also want to have a look at the WindowAnimationTiming draft.
And if you do use setInterval/setTimeout in animations/clocks/etc, always measure the really elapsed time with Date objects (e.g. via Date.now()).

Some browsers don't bother to fire timeout/interval events if the window is unfocused. When the window regains focus, it fires all of them at once. It's a performance decision (Why fire an event every millisecond if the user won't even notice?), and, to my knowledge, there's no way to change this behavior.

Mozilla Firefox and Google Chrome both throttle the speed of ticks that an interval when the tab is inactive (Sources: http://pivotallabs.com/users/mrushakoff/blog/articles/2035-chrome-and-firefox-throttle-settimeout-setinterval-in-inactive-tabs, https://stackoverflow.com/a/6032591/1756941)
And so, as this answer suggests, capture the date and check it against the tick, to verify if it's working correctly, else compensate for the missing time..
JS:
var tick = 20;
var newLeft=0;
var box2 = document.getElementById("box2");
var remove = setInterval(function() {
box2.style.left = "0px";
var now, before = new Date();
var move = setInterval(function() {
now = new Date();
var elapsedTime = (now.getTime() - before.getTime());
if (elapsedTime > tick) {
console.log((Math.floor(elapsedTime / tick) * 5));
newLeft = Math.min(parseInt(box2.style.left) + (Math.floor(elapsedTime / tick) * 5), 850) + "px";
}
else {
newLeft = Math.min(parseInt(box2.style.left) + 5, 850) + "px";
}
box2.style.left = newLeft;
before = new Date();
if (newLeft == "850px"){clearInterval(move);}
}, tick);
}, 5000);
​
JSFiddle: http://jsfiddle.net/3jY8h/1/

Related

is there a way to continue running intervals in javascript even after changing tabs or minimizing the window? [duplicate]

I have a setInterval running a piece of code 30 times a second. This works great, however when I select another tab (so that the tab with my code becomes inactive), the setInterval is set to an idle state for some reason.
I made this simplified test case (http://jsfiddle.net/7f6DX/3/):
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.css("left", a)
}, 1000 / 30);
If you run this code and then switch to another tab, wait a few seconds and go back, the animation continues at the point it was when you switched to the other tab.
So the animation isn't running 30 times a second in case the tab is inactive. This can be confirmed by counting the amount of times the setInterval function is called each second - this will not be 30 but just 1 or 2 if the tab is inactive.
I guess that this is done by design so as to improve system performance, but is there any way to disable this behavior?
It’s actually a disadvantage in my scenario.
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
If the values of your transition were calculated using real time elapsed between frames instead fixed increments on each interval, you not only workaround this issue but also can achieve a smother animation by using requestAnimationFrame as it can get up to 60fps if the processor isn't very busy.
Here's a vanilla JavaScript example of an animated property transition using requestAnimationFrame:
var target = document.querySelector('div#target')
var startedAt, duration = 3000
var domain = [-100, window.innerWidth]
var range = domain[1] - domain[0]
function start() {
startedAt = Date.now()
updateTarget(0)
requestAnimationFrame(update)
}
function update() {
let elapsedTime = Date.now() - startedAt
// playback is a value between 0 and 1
// being 0 the start of the animation and 1 its end
let playback = elapsedTime / duration
updateTarget(playback)
if (playback > 0 && playback < 1) {
// Queue the next frame
requestAnimationFrame(update)
} else {
// Wait for a while and restart the animation
setTimeout(start, duration/10)
}
}
function updateTarget(playback) {
// Uncomment the line below to reverse the animation
// playback = 1 - playback
// Update the target properties based on the playback position
let position = domain[0] + (playback * range)
target.style.left = position + 'px'
target.style.top = position + 'px'
target.style.transform = 'scale(' + playback * 3 + ')'
}
start()
body {
overflow: hidden;
}
div {
position: absolute;
white-space: nowrap;
}
<div id="target">...HERE WE GO</div>
For Background Tasks (non-UI related)
#UpTheCreek comment:
Fine for presentation issues, but still
there are some things that you need to keep running.
If you have background tasks that needs to be precisely executed at given intervals, you can use HTML5 Web Workers. Take a look at Möhre's answer below for more details...
CSS vs JS "animations"
This problem and many others could be avoided by using CSS transitions/animations instead of JavaScript based animations which adds a considerable overhead. I'd recommend this jQuery plugin that let's you take benefit from CSS transitions just like the animate() methods.
I ran into the same problem with audio fading and HTML5 player. It got stuck when tab became inactive.
So I found out a WebWorker is allowed to use intervals/timeouts without limitation. I use it to post "ticks" to the main javascript.
WebWorkers Code:
var fading = false;
var interval;
self.addEventListener('message', function(e){
switch (e.data) {
case 'start':
if (!fading){
fading = true;
interval = setInterval(function(){
self.postMessage('tick');
}, 50);
}
break;
case 'stop':
clearInterval(interval);
fading = false;
break;
};
}, false);
Main Javascript:
var player = new Audio();
player.fader = new Worker('js/fader.js');
player.faderPosition = 0.0;
player.faderTargetVolume = 1.0;
player.faderCallback = function(){};
player.fadeTo = function(volume, func){
console.log('fadeTo called');
if (func) this.faderCallback = func;
this.faderTargetVolume = volume;
this.fader.postMessage('start');
}
player.fader.addEventListener('message', function(e){
console.log('fader tick');
if (player.faderTargetVolume > player.volume){
player.faderPosition -= 0.02;
} else {
player.faderPosition += 0.02;
}
var newVolume = Math.pow(player.faderPosition - 1, 2);
if (newVolume > 0.999){
player.volume = newVolume = 1.0;
player.fader.postMessage('stop');
player.faderCallback();
} else if (newVolume < 0.001) {
player.volume = newVolume = 0.0;
player.fader.postMessage('stop');
player.faderCallback();
} else {
player.volume = newVolume;
}
});
There is a solution to use Web Workers (as mentioned before), because they run in separate process and are not slowed down
I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval.
Just include it before all your code.
more info here
Both setInterval and requestAnimationFrame don't work when tab is inactive or work but not at the right periods. A solution is to use another source for time events. For example web sockets or web workers are two event sources that work fine while tab is inactive. So no need to move all of your code to a web worker, just use worker as a time event source:
// worker.js
setInterval(function() {
postMessage('');
}, 1000 / 50);
.
var worker = new Worker('worker.js');
var t1 = 0;
worker.onmessage = function() {
var t2 = new Date().getTime();
console.log('fps =', 1000 / (t2 - t1) | 0);
t1 = t2;
}
jsfiddle link of this sample.
Just do this:
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);
Inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) will stop all buffered events and execute immediatly only the last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
For me it's not important to play audio in the background like for others here, my problem was that I had some animations and they acted like crazy when you were in other tabs and coming back to them. My solution was putting these animations inside if that is preventing inactive tab:
if (!document.hidden){ //your animation code here }
thanks to that my animation was running only if tab was active.
I hope this will help someone with my case.
I think that a best understanding about this problem is in this example: http://jsfiddle.net/TAHDb/
I am doing a simple thing here:
Have a interval of 1 sec and each time hide the first span and move it to last, and show the 2nd span.
If you stay on page it works as it is supposed.
But if you hide the tab for some seconds, when you get back you will see a weired thing.
Its like all events that didn't ucur during the time you were inactive now will ocur all in 1 time. so for some few seconds you will get like X events. they are so quick that its possible to see all 6 spans at once.
So it seams chrome only delays the events, so when you get back all events will occur but all at once...
A pratical application were this ocur iss for a simple slideshow. Imagine the numbers being Images, and if user stay with tab hidden when he came back he will see all imgs floating, Totally mesed.
To fix this use the stop(true,true) like pimvdb told.
THis will clear the event queue.
Heavily influenced by Ruslan Tushov's library, I've created my own small library. Just add the script in the <head> and it will patch setInterval and setTimeout with ones that use WebWorker.
Playing an audio file ensures full background Javascript performance for the time being
For me, it was the simplest and least intrusive solution - apart from playing a faint / almost-empty sound, there are no other potential side effects
You can find the details here: https://stackoverflow.com/a/51191818/914546
(From other answers, I see that some people use different properties of the Audio tag, I do wonder whether it's possible to use the Audio tag for full performance, without actually playing something)
I bring here a simple solution for anyone who is trying to get around this problem in a timer function, where as #kbtzr mentioned in another answer we can use the Date object instead of fixed increments to calculate the time that has passed since the beginning, which will work even if you are out from the application's tab.
This is the example HTML.
<body>
<p id="time"></p>
</body>
Then this JavaScript:
let display = document.querySelector('#time')
let interval
let time
function startTimer() {
let initialTime = new Date().getTime()
interval = setInterval(() => {
let now = new Date().getTime()
time = (now - initialTime) + 10
display.innerText = `${Math.floor((time / (60 * 1000)) % 60)}:${Math.floor((time / 1000) % 60)}:${Math.floor((time / 10) % 100)}`
}, 10)
}
startTimer()
That way, even if the interval value is increased for performance reasons of inactive tabs, the calculation made will guarantee the correct time. This is a vanilla code, but I used this logic in my React application, and you can modify it for wherever you need as well.
It is quite old question but I encountered the same issue.
If you run your web on chrome, you could read through this post Background Tabs in Chrome 57.
Basically the interval timer could run if it haven't run out of the timer budget.
The consumption of budget is based on CPU time usage of the task inside timer.
Based on my scenario, I draw video to canvas and transport to WebRTC.
The webrtc video connection would keep updating even the tab is inactive.
However you have to use setInterval instead of requestAnimationFrame but itt is not recommended for UI rendering though.
It would be better to listen visibilityChange event and change render mechenism accordingly.
Besides, you could try what Kaan Soral suggests and it should works based on the documentation.
I modified Lacerda's response, by adding a functioning UI.
I added start/resume/pause/stop actions.
const
timer = document.querySelector('.timer'),
timerDisplay = timer.querySelector('.timer-display'),
toggleAction = timer.querySelector('[data-action="toggle"]'),
stopAction = timer.querySelector('[data-action="stop"]'),
tickRate = 10;
let intervalId, initialTime, pauseTime = 0;
const now = () => new Date().getTime();
const formatTime = (hours, minutes, seconds) =>
[hours, minutes, seconds]
.map(v => `${isNaN(v) ? 0 : v}`.padStart(2, '0'))
.join(':');
const update = () => {
let
time = (now() - initialTime) + 10,
hours = Math.floor((time / (60000)) % 60),
minutes = Math.floor((time / 1000) % 60),
seconds = Math.floor((time / 10) % 100);
timerDisplay.textContent = formatTime(hours, minutes, seconds);
};
const
startTimer = () => {
initialTime = now();
intervalId = setInterval(update, tickRate);
},
resumeTimer = () => {
initialTime = now() - (pauseTime - initialTime);
intervalId = setInterval(update, tickRate);
},
pauseTimer = () => {
clearInterval(intervalId);
intervalId = null;
pauseTime = now();
},
stopTimer = () => {
clearInterval(intervalId);
intervalId = null;
initialTime = undefined;
pauseTime = 0;
},
restartTimer = () => {
stopTimer();
startTimer();
};
const setButtonState = (button, state, text) => {
button.dataset.state = state;
button.textContent = text;
};
const
handleToggle = (e) => {
switch (e.target.dataset.state) {
case 'pause':
setButtonState(e.target, 'resume', 'Resume');
pauseTimer();
break;
case 'resume':
setButtonState(e.target, 'pause', 'Pause');
resumeTimer();
break;
default:
setButtonState(e.target, 'pause', 'Pause');
restartTimer();
}
},
handleStop = (e) => {
stopTimer();
update();
setButtonState(toggleAction, 'initial', 'Start');
};
toggleAction.addEventListener('click', handleToggle);
stopAction.addEventListener('click', handleStop);
update();
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
background: #000;
}
.timer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5em;
}
.timer .timer-display {
font-family: monospace;
font-size: 3em;
background: #111;
color: #8F8;
border: thin solid #222;
padding: 0.25em;
}
.timer .timer-actions {
display: flex;
justify-content: center;
gap: 0.5em;
}
.timer .timer-actions button[data-action] {
font-family: monospace;
width: 6em;
border: thin solid #444;
background: #222;
color: #EEE;
padding: 0.5em;
cursor: pointer;
text-transform: uppercase;
}
.timer .timer-actions button[data-action]:hover {
background: #444;
border: thin solid #666;
color: #FFF;
}
<div class="timer">
<div class="timer-display"></div>
<div class="timer-actions">
<button data-action="toggle" data-state="initial">Start</button>
<button data-action="stop">Stop</button>
</div>
</div>
Note: This solution is not suitable if you like your interval works on the background, for example, playing audio or something else. But if you are confused for example about your animation not working properly when coming back to your page or tab, this is a good solution.
There are many ways to achieve this goal, maybe the "WebWorkers" is the most standard one but certainly, it's not the easiest and handy one, especially If you don't have enough Time, so you can try this way:
The basic concept:
build a name for your interval(or animation) and set your interval(animation), so it would run when user first time open your page : var interval_id = setInterval(your_func, 3000);
by $(window).focus(function() {}); and $(window).blur(function() {}); you can clearInterval(interval_id) everytime browser(tab) is deactived and ReRun your interval(animation) everytime browser(tab) would acive again by interval_id = setInterval();
Sample code:
var interval_id = setInterval(your_func, 3000);
$(window).focus(function() {
interval_id = setInterval(your_func, 3000);
});
$(window).blur(function() {
clearInterval(interval_id);
interval_id = 0;
});
Here's my rough solution
(function(){
var index = 1;
var intervals = {},
timeouts = {};
function postMessageHandler(e) {
window.postMessage('', "*");
var now = new Date().getTime();
sysFunc._each.call(timeouts, function(ind, obj) {
var targetTime = obj[1];
if (now >= targetTime) {
obj[0]();
delete timeouts[ind];
}
});
sysFunc._each.call(intervals, function(ind, obj) {
var startTime = obj[1];
var func = obj[0];
var ms = obj[2];
if (now >= startTime + ms) {
func();
obj[1] = new Date().getTime();
}
});
}
window.addEventListener("message", postMessageHandler, true);
window.postMessage('', "*");
function _setTimeout(func, ms) {
timeouts[index] = [func, new Date().getTime() + ms];
return index++;
}
function _setInterval(func, ms) {
intervals[index] = [func, new Date().getTime(), ms];
return index++;
}
function _clearInterval(ind) {
if (intervals[ind]) {
delete intervals[ind]
}
}
function _clearTimeout(ind) {
if (timeouts[ind]) {
delete timeouts[ind]
}
}
var intervalIndex = _setInterval(function() {
console.log('every 100ms');
}, 100);
_setTimeout(function() {
console.log('run after 200ms');
}, 200);
_setTimeout(function() {
console.log('closing the one that\'s 100ms');
_clearInterval(intervalIndex)
}, 2000);
window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();
I was able to call my callback function at minimum of 250ms using audio tag and handling its ontimeupdate event. Its called 3-4 times in a second. Its better than one second lagging setTimeout
There is a workaround for this problem, although actually the tab must be made active in some window.
Make your inactive tab a separate browser window.
Don't make any other window maximized (unless the maximized window is behind yours).
This should give the browser the impression of always being active.
This is bit cumbersome, but also a quick win. Provided one has control over windows arrangement.

Why are my setInterval and setTimeout Animations behaving unexpectedly when switching windows [duplicate]

I have a setInterval running a piece of code 30 times a second. This works great, however when I select another tab (so that the tab with my code becomes inactive), the setInterval is set to an idle state for some reason.
I made this simplified test case (http://jsfiddle.net/7f6DX/3/):
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.css("left", a)
}, 1000 / 30);
If you run this code and then switch to another tab, wait a few seconds and go back, the animation continues at the point it was when you switched to the other tab.
So the animation isn't running 30 times a second in case the tab is inactive. This can be confirmed by counting the amount of times the setInterval function is called each second - this will not be 30 but just 1 or 2 if the tab is inactive.
I guess that this is done by design so as to improve system performance, but is there any way to disable this behavior?
It’s actually a disadvantage in my scenario.
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
If the values of your transition were calculated using real time elapsed between frames instead fixed increments on each interval, you not only workaround this issue but also can achieve a smother animation by using requestAnimationFrame as it can get up to 60fps if the processor isn't very busy.
Here's a vanilla JavaScript example of an animated property transition using requestAnimationFrame:
var target = document.querySelector('div#target')
var startedAt, duration = 3000
var domain = [-100, window.innerWidth]
var range = domain[1] - domain[0]
function start() {
startedAt = Date.now()
updateTarget(0)
requestAnimationFrame(update)
}
function update() {
let elapsedTime = Date.now() - startedAt
// playback is a value between 0 and 1
// being 0 the start of the animation and 1 its end
let playback = elapsedTime / duration
updateTarget(playback)
if (playback > 0 && playback < 1) {
// Queue the next frame
requestAnimationFrame(update)
} else {
// Wait for a while and restart the animation
setTimeout(start, duration/10)
}
}
function updateTarget(playback) {
// Uncomment the line below to reverse the animation
// playback = 1 - playback
// Update the target properties based on the playback position
let position = domain[0] + (playback * range)
target.style.left = position + 'px'
target.style.top = position + 'px'
target.style.transform = 'scale(' + playback * 3 + ')'
}
start()
body {
overflow: hidden;
}
div {
position: absolute;
white-space: nowrap;
}
<div id="target">...HERE WE GO</div>
For Background Tasks (non-UI related)
#UpTheCreek comment:
Fine for presentation issues, but still
there are some things that you need to keep running.
If you have background tasks that needs to be precisely executed at given intervals, you can use HTML5 Web Workers. Take a look at Möhre's answer below for more details...
CSS vs JS "animations"
This problem and many others could be avoided by using CSS transitions/animations instead of JavaScript based animations which adds a considerable overhead. I'd recommend this jQuery plugin that let's you take benefit from CSS transitions just like the animate() methods.
I ran into the same problem with audio fading and HTML5 player. It got stuck when tab became inactive.
So I found out a WebWorker is allowed to use intervals/timeouts without limitation. I use it to post "ticks" to the main javascript.
WebWorkers Code:
var fading = false;
var interval;
self.addEventListener('message', function(e){
switch (e.data) {
case 'start':
if (!fading){
fading = true;
interval = setInterval(function(){
self.postMessage('tick');
}, 50);
}
break;
case 'stop':
clearInterval(interval);
fading = false;
break;
};
}, false);
Main Javascript:
var player = new Audio();
player.fader = new Worker('js/fader.js');
player.faderPosition = 0.0;
player.faderTargetVolume = 1.0;
player.faderCallback = function(){};
player.fadeTo = function(volume, func){
console.log('fadeTo called');
if (func) this.faderCallback = func;
this.faderTargetVolume = volume;
this.fader.postMessage('start');
}
player.fader.addEventListener('message', function(e){
console.log('fader tick');
if (player.faderTargetVolume > player.volume){
player.faderPosition -= 0.02;
} else {
player.faderPosition += 0.02;
}
var newVolume = Math.pow(player.faderPosition - 1, 2);
if (newVolume > 0.999){
player.volume = newVolume = 1.0;
player.fader.postMessage('stop');
player.faderCallback();
} else if (newVolume < 0.001) {
player.volume = newVolume = 0.0;
player.fader.postMessage('stop');
player.faderCallback();
} else {
player.volume = newVolume;
}
});
There is a solution to use Web Workers (as mentioned before), because they run in separate process and are not slowed down
I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval.
Just include it before all your code.
more info here
Both setInterval and requestAnimationFrame don't work when tab is inactive or work but not at the right periods. A solution is to use another source for time events. For example web sockets or web workers are two event sources that work fine while tab is inactive. So no need to move all of your code to a web worker, just use worker as a time event source:
// worker.js
setInterval(function() {
postMessage('');
}, 1000 / 50);
.
var worker = new Worker('worker.js');
var t1 = 0;
worker.onmessage = function() {
var t2 = new Date().getTime();
console.log('fps =', 1000 / (t2 - t1) | 0);
t1 = t2;
}
jsfiddle link of this sample.
Just do this:
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);
Inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) will stop all buffered events and execute immediatly only the last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
For me it's not important to play audio in the background like for others here, my problem was that I had some animations and they acted like crazy when you were in other tabs and coming back to them. My solution was putting these animations inside if that is preventing inactive tab:
if (!document.hidden){ //your animation code here }
thanks to that my animation was running only if tab was active.
I hope this will help someone with my case.
I think that a best understanding about this problem is in this example: http://jsfiddle.net/TAHDb/
I am doing a simple thing here:
Have a interval of 1 sec and each time hide the first span and move it to last, and show the 2nd span.
If you stay on page it works as it is supposed.
But if you hide the tab for some seconds, when you get back you will see a weired thing.
Its like all events that didn't ucur during the time you were inactive now will ocur all in 1 time. so for some few seconds you will get like X events. they are so quick that its possible to see all 6 spans at once.
So it seams chrome only delays the events, so when you get back all events will occur but all at once...
A pratical application were this ocur iss for a simple slideshow. Imagine the numbers being Images, and if user stay with tab hidden when he came back he will see all imgs floating, Totally mesed.
To fix this use the stop(true,true) like pimvdb told.
THis will clear the event queue.
Heavily influenced by Ruslan Tushov's library, I've created my own small library. Just add the script in the <head> and it will patch setInterval and setTimeout with ones that use WebWorker.
Playing an audio file ensures full background Javascript performance for the time being
For me, it was the simplest and least intrusive solution - apart from playing a faint / almost-empty sound, there are no other potential side effects
You can find the details here: https://stackoverflow.com/a/51191818/914546
(From other answers, I see that some people use different properties of the Audio tag, I do wonder whether it's possible to use the Audio tag for full performance, without actually playing something)
I bring here a simple solution for anyone who is trying to get around this problem in a timer function, where as #kbtzr mentioned in another answer we can use the Date object instead of fixed increments to calculate the time that has passed since the beginning, which will work even if you are out from the application's tab.
This is the example HTML.
<body>
<p id="time"></p>
</body>
Then this JavaScript:
let display = document.querySelector('#time')
let interval
let time
function startTimer() {
let initialTime = new Date().getTime()
interval = setInterval(() => {
let now = new Date().getTime()
time = (now - initialTime) + 10
display.innerText = `${Math.floor((time / (60 * 1000)) % 60)}:${Math.floor((time / 1000) % 60)}:${Math.floor((time / 10) % 100)}`
}, 10)
}
startTimer()
That way, even if the interval value is increased for performance reasons of inactive tabs, the calculation made will guarantee the correct time. This is a vanilla code, but I used this logic in my React application, and you can modify it for wherever you need as well.
It is quite old question but I encountered the same issue.
If you run your web on chrome, you could read through this post Background Tabs in Chrome 57.
Basically the interval timer could run if it haven't run out of the timer budget.
The consumption of budget is based on CPU time usage of the task inside timer.
Based on my scenario, I draw video to canvas and transport to WebRTC.
The webrtc video connection would keep updating even the tab is inactive.
However you have to use setInterval instead of requestAnimationFrame but itt is not recommended for UI rendering though.
It would be better to listen visibilityChange event and change render mechenism accordingly.
Besides, you could try what Kaan Soral suggests and it should works based on the documentation.
I modified Lacerda's response, by adding a functioning UI.
I added start/resume/pause/stop actions.
const
timer = document.querySelector('.timer'),
timerDisplay = timer.querySelector('.timer-display'),
toggleAction = timer.querySelector('[data-action="toggle"]'),
stopAction = timer.querySelector('[data-action="stop"]'),
tickRate = 10;
let intervalId, initialTime, pauseTime = 0;
const now = () => new Date().getTime();
const formatTime = (hours, minutes, seconds) =>
[hours, minutes, seconds]
.map(v => `${isNaN(v) ? 0 : v}`.padStart(2, '0'))
.join(':');
const update = () => {
let
time = (now() - initialTime) + 10,
hours = Math.floor((time / (60000)) % 60),
minutes = Math.floor((time / 1000) % 60),
seconds = Math.floor((time / 10) % 100);
timerDisplay.textContent = formatTime(hours, minutes, seconds);
};
const
startTimer = () => {
initialTime = now();
intervalId = setInterval(update, tickRate);
},
resumeTimer = () => {
initialTime = now() - (pauseTime - initialTime);
intervalId = setInterval(update, tickRate);
},
pauseTimer = () => {
clearInterval(intervalId);
intervalId = null;
pauseTime = now();
},
stopTimer = () => {
clearInterval(intervalId);
intervalId = null;
initialTime = undefined;
pauseTime = 0;
},
restartTimer = () => {
stopTimer();
startTimer();
};
const setButtonState = (button, state, text) => {
button.dataset.state = state;
button.textContent = text;
};
const
handleToggle = (e) => {
switch (e.target.dataset.state) {
case 'pause':
setButtonState(e.target, 'resume', 'Resume');
pauseTimer();
break;
case 'resume':
setButtonState(e.target, 'pause', 'Pause');
resumeTimer();
break;
default:
setButtonState(e.target, 'pause', 'Pause');
restartTimer();
}
},
handleStop = (e) => {
stopTimer();
update();
setButtonState(toggleAction, 'initial', 'Start');
};
toggleAction.addEventListener('click', handleToggle);
stopAction.addEventListener('click', handleStop);
update();
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
background: #000;
}
.timer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5em;
}
.timer .timer-display {
font-family: monospace;
font-size: 3em;
background: #111;
color: #8F8;
border: thin solid #222;
padding: 0.25em;
}
.timer .timer-actions {
display: flex;
justify-content: center;
gap: 0.5em;
}
.timer .timer-actions button[data-action] {
font-family: monospace;
width: 6em;
border: thin solid #444;
background: #222;
color: #EEE;
padding: 0.5em;
cursor: pointer;
text-transform: uppercase;
}
.timer .timer-actions button[data-action]:hover {
background: #444;
border: thin solid #666;
color: #FFF;
}
<div class="timer">
<div class="timer-display"></div>
<div class="timer-actions">
<button data-action="toggle" data-state="initial">Start</button>
<button data-action="stop">Stop</button>
</div>
</div>
Note: This solution is not suitable if you like your interval works on the background, for example, playing audio or something else. But if you are confused for example about your animation not working properly when coming back to your page or tab, this is a good solution.
There are many ways to achieve this goal, maybe the "WebWorkers" is the most standard one but certainly, it's not the easiest and handy one, especially If you don't have enough Time, so you can try this way:
The basic concept:
build a name for your interval(or animation) and set your interval(animation), so it would run when user first time open your page : var interval_id = setInterval(your_func, 3000);
by $(window).focus(function() {}); and $(window).blur(function() {}); you can clearInterval(interval_id) everytime browser(tab) is deactived and ReRun your interval(animation) everytime browser(tab) would acive again by interval_id = setInterval();
Sample code:
var interval_id = setInterval(your_func, 3000);
$(window).focus(function() {
interval_id = setInterval(your_func, 3000);
});
$(window).blur(function() {
clearInterval(interval_id);
interval_id = 0;
});
Here's my rough solution
(function(){
var index = 1;
var intervals = {},
timeouts = {};
function postMessageHandler(e) {
window.postMessage('', "*");
var now = new Date().getTime();
sysFunc._each.call(timeouts, function(ind, obj) {
var targetTime = obj[1];
if (now >= targetTime) {
obj[0]();
delete timeouts[ind];
}
});
sysFunc._each.call(intervals, function(ind, obj) {
var startTime = obj[1];
var func = obj[0];
var ms = obj[2];
if (now >= startTime + ms) {
func();
obj[1] = new Date().getTime();
}
});
}
window.addEventListener("message", postMessageHandler, true);
window.postMessage('', "*");
function _setTimeout(func, ms) {
timeouts[index] = [func, new Date().getTime() + ms];
return index++;
}
function _setInterval(func, ms) {
intervals[index] = [func, new Date().getTime(), ms];
return index++;
}
function _clearInterval(ind) {
if (intervals[ind]) {
delete intervals[ind]
}
}
function _clearTimeout(ind) {
if (timeouts[ind]) {
delete timeouts[ind]
}
}
var intervalIndex = _setInterval(function() {
console.log('every 100ms');
}, 100);
_setTimeout(function() {
console.log('run after 200ms');
}, 200);
_setTimeout(function() {
console.log('closing the one that\'s 100ms');
_clearInterval(intervalIndex)
}, 2000);
window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();
I was able to call my callback function at minimum of 250ms using audio tag and handling its ontimeupdate event. Its called 3-4 times in a second. Its better than one second lagging setTimeout
There is a workaround for this problem, although actually the tab must be made active in some window.
Make your inactive tab a separate browser window.
Don't make any other window maximized (unless the maximized window is behind yours).
This should give the browser the impression of always being active.
This is bit cumbersome, but also a quick win. Provided one has control over windows arrangement.

Timer Doesn't Count In Background Tab [duplicate]

I have a setInterval running a piece of code 30 times a second. This works great, however when I select another tab (so that the tab with my code becomes inactive), the setInterval is set to an idle state for some reason.
I made this simplified test case (http://jsfiddle.net/7f6DX/3/):
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.css("left", a)
}, 1000 / 30);
If you run this code and then switch to another tab, wait a few seconds and go back, the animation continues at the point it was when you switched to the other tab.
So the animation isn't running 30 times a second in case the tab is inactive. This can be confirmed by counting the amount of times the setInterval function is called each second - this will not be 30 but just 1 or 2 if the tab is inactive.
I guess that this is done by design so as to improve system performance, but is there any way to disable this behavior?
It’s actually a disadvantage in my scenario.
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
If the values of your transition were calculated using real time elapsed between frames instead fixed increments on each interval, you not only workaround this issue but also can achieve a smother animation by using requestAnimationFrame as it can get up to 60fps if the processor isn't very busy.
Here's a vanilla JavaScript example of an animated property transition using requestAnimationFrame:
var target = document.querySelector('div#target')
var startedAt, duration = 3000
var domain = [-100, window.innerWidth]
var range = domain[1] - domain[0]
function start() {
startedAt = Date.now()
updateTarget(0)
requestAnimationFrame(update)
}
function update() {
let elapsedTime = Date.now() - startedAt
// playback is a value between 0 and 1
// being 0 the start of the animation and 1 its end
let playback = elapsedTime / duration
updateTarget(playback)
if (playback > 0 && playback < 1) {
// Queue the next frame
requestAnimationFrame(update)
} else {
// Wait for a while and restart the animation
setTimeout(start, duration/10)
}
}
function updateTarget(playback) {
// Uncomment the line below to reverse the animation
// playback = 1 - playback
// Update the target properties based on the playback position
let position = domain[0] + (playback * range)
target.style.left = position + 'px'
target.style.top = position + 'px'
target.style.transform = 'scale(' + playback * 3 + ')'
}
start()
body {
overflow: hidden;
}
div {
position: absolute;
white-space: nowrap;
}
<div id="target">...HERE WE GO</div>
For Background Tasks (non-UI related)
#UpTheCreek comment:
Fine for presentation issues, but still
there are some things that you need to keep running.
If you have background tasks that needs to be precisely executed at given intervals, you can use HTML5 Web Workers. Take a look at Möhre's answer below for more details...
CSS vs JS "animations"
This problem and many others could be avoided by using CSS transitions/animations instead of JavaScript based animations which adds a considerable overhead. I'd recommend this jQuery plugin that let's you take benefit from CSS transitions just like the animate() methods.
I ran into the same problem with audio fading and HTML5 player. It got stuck when tab became inactive.
So I found out a WebWorker is allowed to use intervals/timeouts without limitation. I use it to post "ticks" to the main javascript.
WebWorkers Code:
var fading = false;
var interval;
self.addEventListener('message', function(e){
switch (e.data) {
case 'start':
if (!fading){
fading = true;
interval = setInterval(function(){
self.postMessage('tick');
}, 50);
}
break;
case 'stop':
clearInterval(interval);
fading = false;
break;
};
}, false);
Main Javascript:
var player = new Audio();
player.fader = new Worker('js/fader.js');
player.faderPosition = 0.0;
player.faderTargetVolume = 1.0;
player.faderCallback = function(){};
player.fadeTo = function(volume, func){
console.log('fadeTo called');
if (func) this.faderCallback = func;
this.faderTargetVolume = volume;
this.fader.postMessage('start');
}
player.fader.addEventListener('message', function(e){
console.log('fader tick');
if (player.faderTargetVolume > player.volume){
player.faderPosition -= 0.02;
} else {
player.faderPosition += 0.02;
}
var newVolume = Math.pow(player.faderPosition - 1, 2);
if (newVolume > 0.999){
player.volume = newVolume = 1.0;
player.fader.postMessage('stop');
player.faderCallback();
} else if (newVolume < 0.001) {
player.volume = newVolume = 0.0;
player.fader.postMessage('stop');
player.faderCallback();
} else {
player.volume = newVolume;
}
});
There is a solution to use Web Workers (as mentioned before), because they run in separate process and are not slowed down
I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval.
Just include it before all your code.
more info here
Both setInterval and requestAnimationFrame don't work when tab is inactive or work but not at the right periods. A solution is to use another source for time events. For example web sockets or web workers are two event sources that work fine while tab is inactive. So no need to move all of your code to a web worker, just use worker as a time event source:
// worker.js
setInterval(function() {
postMessage('');
}, 1000 / 50);
.
var worker = new Worker('worker.js');
var t1 = 0;
worker.onmessage = function() {
var t2 = new Date().getTime();
console.log('fps =', 1000 / (t2 - t1) | 0);
t1 = t2;
}
jsfiddle link of this sample.
Just do this:
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);
Inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) will stop all buffered events and execute immediatly only the last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
For me it's not important to play audio in the background like for others here, my problem was that I had some animations and they acted like crazy when you were in other tabs and coming back to them. My solution was putting these animations inside if that is preventing inactive tab:
if (!document.hidden){ //your animation code here }
thanks to that my animation was running only if tab was active.
I hope this will help someone with my case.
I think that a best understanding about this problem is in this example: http://jsfiddle.net/TAHDb/
I am doing a simple thing here:
Have a interval of 1 sec and each time hide the first span and move it to last, and show the 2nd span.
If you stay on page it works as it is supposed.
But if you hide the tab for some seconds, when you get back you will see a weired thing.
Its like all events that didn't ucur during the time you were inactive now will ocur all in 1 time. so for some few seconds you will get like X events. they are so quick that its possible to see all 6 spans at once.
So it seams chrome only delays the events, so when you get back all events will occur but all at once...
A pratical application were this ocur iss for a simple slideshow. Imagine the numbers being Images, and if user stay with tab hidden when he came back he will see all imgs floating, Totally mesed.
To fix this use the stop(true,true) like pimvdb told.
THis will clear the event queue.
Heavily influenced by Ruslan Tushov's library, I've created my own small library. Just add the script in the <head> and it will patch setInterval and setTimeout with ones that use WebWorker.
Playing an audio file ensures full background Javascript performance for the time being
For me, it was the simplest and least intrusive solution - apart from playing a faint / almost-empty sound, there are no other potential side effects
You can find the details here: https://stackoverflow.com/a/51191818/914546
(From other answers, I see that some people use different properties of the Audio tag, I do wonder whether it's possible to use the Audio tag for full performance, without actually playing something)
I bring here a simple solution for anyone who is trying to get around this problem in a timer function, where as #kbtzr mentioned in another answer we can use the Date object instead of fixed increments to calculate the time that has passed since the beginning, which will work even if you are out from the application's tab.
This is the example HTML.
<body>
<p id="time"></p>
</body>
Then this JavaScript:
let display = document.querySelector('#time')
let interval
let time
function startTimer() {
let initialTime = new Date().getTime()
interval = setInterval(() => {
let now = new Date().getTime()
time = (now - initialTime) + 10
display.innerText = `${Math.floor((time / (60 * 1000)) % 60)}:${Math.floor((time / 1000) % 60)}:${Math.floor((time / 10) % 100)}`
}, 10)
}
startTimer()
That way, even if the interval value is increased for performance reasons of inactive tabs, the calculation made will guarantee the correct time. This is a vanilla code, but I used this logic in my React application, and you can modify it for wherever you need as well.
It is quite old question but I encountered the same issue.
If you run your web on chrome, you could read through this post Background Tabs in Chrome 57.
Basically the interval timer could run if it haven't run out of the timer budget.
The consumption of budget is based on CPU time usage of the task inside timer.
Based on my scenario, I draw video to canvas and transport to WebRTC.
The webrtc video connection would keep updating even the tab is inactive.
However you have to use setInterval instead of requestAnimationFrame but itt is not recommended for UI rendering though.
It would be better to listen visibilityChange event and change render mechenism accordingly.
Besides, you could try what Kaan Soral suggests and it should works based on the documentation.
I modified Lacerda's response, by adding a functioning UI.
I added start/resume/pause/stop actions.
const
timer = document.querySelector('.timer'),
timerDisplay = timer.querySelector('.timer-display'),
toggleAction = timer.querySelector('[data-action="toggle"]'),
stopAction = timer.querySelector('[data-action="stop"]'),
tickRate = 10;
let intervalId, initialTime, pauseTime = 0;
const now = () => new Date().getTime();
const formatTime = (hours, minutes, seconds) =>
[hours, minutes, seconds]
.map(v => `${isNaN(v) ? 0 : v}`.padStart(2, '0'))
.join(':');
const update = () => {
let
time = (now() - initialTime) + 10,
hours = Math.floor((time / (60000)) % 60),
minutes = Math.floor((time / 1000) % 60),
seconds = Math.floor((time / 10) % 100);
timerDisplay.textContent = formatTime(hours, minutes, seconds);
};
const
startTimer = () => {
initialTime = now();
intervalId = setInterval(update, tickRate);
},
resumeTimer = () => {
initialTime = now() - (pauseTime - initialTime);
intervalId = setInterval(update, tickRate);
},
pauseTimer = () => {
clearInterval(intervalId);
intervalId = null;
pauseTime = now();
},
stopTimer = () => {
clearInterval(intervalId);
intervalId = null;
initialTime = undefined;
pauseTime = 0;
},
restartTimer = () => {
stopTimer();
startTimer();
};
const setButtonState = (button, state, text) => {
button.dataset.state = state;
button.textContent = text;
};
const
handleToggle = (e) => {
switch (e.target.dataset.state) {
case 'pause':
setButtonState(e.target, 'resume', 'Resume');
pauseTimer();
break;
case 'resume':
setButtonState(e.target, 'pause', 'Pause');
resumeTimer();
break;
default:
setButtonState(e.target, 'pause', 'Pause');
restartTimer();
}
},
handleStop = (e) => {
stopTimer();
update();
setButtonState(toggleAction, 'initial', 'Start');
};
toggleAction.addEventListener('click', handleToggle);
stopAction.addEventListener('click', handleStop);
update();
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
background: #000;
}
.timer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5em;
}
.timer .timer-display {
font-family: monospace;
font-size: 3em;
background: #111;
color: #8F8;
border: thin solid #222;
padding: 0.25em;
}
.timer .timer-actions {
display: flex;
justify-content: center;
gap: 0.5em;
}
.timer .timer-actions button[data-action] {
font-family: monospace;
width: 6em;
border: thin solid #444;
background: #222;
color: #EEE;
padding: 0.5em;
cursor: pointer;
text-transform: uppercase;
}
.timer .timer-actions button[data-action]:hover {
background: #444;
border: thin solid #666;
color: #FFF;
}
<div class="timer">
<div class="timer-display"></div>
<div class="timer-actions">
<button data-action="toggle" data-state="initial">Start</button>
<button data-action="stop">Stop</button>
</div>
</div>
Note: This solution is not suitable if you like your interval works on the background, for example, playing audio or something else. But if you are confused for example about your animation not working properly when coming back to your page or tab, this is a good solution.
There are many ways to achieve this goal, maybe the "WebWorkers" is the most standard one but certainly, it's not the easiest and handy one, especially If you don't have enough Time, so you can try this way:
The basic concept:
build a name for your interval(or animation) and set your interval(animation), so it would run when user first time open your page : var interval_id = setInterval(your_func, 3000);
by $(window).focus(function() {}); and $(window).blur(function() {}); you can clearInterval(interval_id) everytime browser(tab) is deactived and ReRun your interval(animation) everytime browser(tab) would acive again by interval_id = setInterval();
Sample code:
var interval_id = setInterval(your_func, 3000);
$(window).focus(function() {
interval_id = setInterval(your_func, 3000);
});
$(window).blur(function() {
clearInterval(interval_id);
interval_id = 0;
});
Here's my rough solution
(function(){
var index = 1;
var intervals = {},
timeouts = {};
function postMessageHandler(e) {
window.postMessage('', "*");
var now = new Date().getTime();
sysFunc._each.call(timeouts, function(ind, obj) {
var targetTime = obj[1];
if (now >= targetTime) {
obj[0]();
delete timeouts[ind];
}
});
sysFunc._each.call(intervals, function(ind, obj) {
var startTime = obj[1];
var func = obj[0];
var ms = obj[2];
if (now >= startTime + ms) {
func();
obj[1] = new Date().getTime();
}
});
}
window.addEventListener("message", postMessageHandler, true);
window.postMessage('', "*");
function _setTimeout(func, ms) {
timeouts[index] = [func, new Date().getTime() + ms];
return index++;
}
function _setInterval(func, ms) {
intervals[index] = [func, new Date().getTime(), ms];
return index++;
}
function _clearInterval(ind) {
if (intervals[ind]) {
delete intervals[ind]
}
}
function _clearTimeout(ind) {
if (timeouts[ind]) {
delete timeouts[ind]
}
}
var intervalIndex = _setInterval(function() {
console.log('every 100ms');
}, 100);
_setTimeout(function() {
console.log('run after 200ms');
}, 200);
_setTimeout(function() {
console.log('closing the one that\'s 100ms');
_clearInterval(intervalIndex)
}, 2000);
window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();
I was able to call my callback function at minimum of 250ms using audio tag and handling its ontimeupdate event. Its called 3-4 times in a second. Its better than one second lagging setTimeout
There is a workaround for this problem, although actually the tab must be made active in some window.
Make your inactive tab a separate browser window.
Don't make any other window maximized (unless the maximized window is behind yours).
This should give the browser the impression of always being active.
This is bit cumbersome, but also a quick win. Provided one has control over windows arrangement.

Animation (bound to scrollTop) only finishes when I stop scrolling

My animation event nearly pauses or lets say gets significantly slower at completing when scroll reaches more than 15 percent. Why is that? If should animate to the left but instead it does so only when I stop scrolling.
$(window).scroll(function ()
{
var content_height = $(document).height();
var content_scroll_pos = $(window).scrollTop();
var percentage_value = content_scroll_pos * 100 / content_height;
if(percentage_value > 15)
{
TweenMax.to(".bar", 3, {right:"0", ease:Bounce.easeOut})
}
else
{
TweenMax.to(".bar", 2, {right:"-125%", ease:Power2.easeOut})
}
});
Here a Demo of the solution with explaining comments:
// This gets called _every time_, you scroll a little bit ("every time" as in "every frame").
// So we introduce a new variable that acts as a filter and only lets the function trigger, once the status changes.
// 0 = not changed (it is, where it was on page loading)
// 1 = out of the screen
// 2 = back in the screen
var status = 0;
$(window).scroll(function ()
{
var content_height = $(document).height();
var content_scroll_pos = $(window).scrollTop();
var percentage_value = content_scroll_pos * 100 / content_height;
var newStatus = percentage_value > 15 ? 2 : 1;
if(newStatus == status)
return;
switch(newStatus) {
case 1:
TweenMax.to(".bar", 2, {right:"-125%", ease:Power2.easeOut});
break;
case 2:
// because this function got called all the time, the animation started all over again, each frame.
// And becase the animation starts slowly it stayed slow as long as the user scrolled.
TweenMax.to(".bar", 3, {right:"0", ease:Bounce.easeOut});
break;
}
status = newStatus;
});
.foo {
height: 2000px;
}
.bar {
background-color: red;
height: 50px;
width: 50px;
position: fixed;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="foo">
Test<br />
Test
<div class="bar">
</div>
</div>
By the way:
Because of the nasty per-frame thing, you can improve your performance significantly, if you do not calculate the value of $(document), $(document).height() and $(window) inside that function. I would suggest, scoping all of this code in a wrap like
(function() {
var jDocument = $(document),
content_height = jDocument.height(),
jWindow = $(window),
status = 0;
// [CODE HERE, using jWindow instead of $(window)]
})();
Which also solves the problem, that status would either need a long and complicated name otherwise or would be in danger of getting overwritten by the code of anybody else.
PS: I have to admit, that I don't like your animation in the current state. It bounces too much for me - even that much too much, that I can not see it bouncing at all. To me it seemed like it appeared and disappeared, the first time, I saw it.

jQuery scroll event: how to determine amount scrolled (scroll delta) in pixels?

I have this event:
$(window).scroll(function(e){
console.log(e);
})
I want to know, how much I have scroll value in pixels, because I think, scroll value depends from window size and screen resolution.
Function parameter e does not contains this information.
I can store $(window).scrollTop() after every scroll and calculate difference, but can I do it differently?
The "scroll value" does not depend on the window size or screen resolution. The "scroll value" is simply the number of pixels scrolled.
However, whether you are able to scroll at all, and the amount you can scroll is based on available real estate for the container and the dimensions of the content within the container (in this case the container is document.documentElement, or document.body for older browsers).
You are correct that the scroll event does not contain this information. It does not provide a delta property to indicate the number of pixels scrolled. This is true for the native scroll event and the jQuery scroll event. This seems like it would be a useful feature to have, similar to how mousewheel events provide properties for X and Y delta.
I do not know, and will not speculate upon, why the powers-that-be did not provide a delta property for scroll, but that is out of scope for this question (feel free to post a separate question about this).
The method you are using of storing scrollTop in a variable and comparing it to the current scrollTop is the best (and only) method I have found. However, you can simplify this a bit by extending jQuery to provide a new custom event, per this article: http://learn.jquery.com/events/event-extensions/
Here is an example extension I created that works with window / document scrolling. It is a custom event called scrolldelta that automatically tracks the X and Y delta (as scrollLeftDelta and scrollTopDelta, respectively). I have not tried it with other elements; leaving this as exercise for the reader. This works in currrent versions of Chrome and Firefox. It uses the trick for getting the sum of document.documentElement.scrollTop and document.body.scrollTop to handle the bug where Chrome updates body.scrollTop instead of documentElement.scrollTop (IE and FF update documentElement.scrollTop; see https://code.google.com/p/chromium/issues/detail?id=2891).
JSFiddle demo: http://jsfiddle.net/tew9zxc1/
Runnable Snippet (scroll down and click Run code snippet):
// custom 'scrolldelta' event extends 'scroll' event
jQuery.event.special.scrolldelta = {
delegateType: "scroll",
bindType: "scroll",
handle: function (event) {
var handleObj = event.handleObj;
var targetData = jQuery.data(event.target);
var ret = null;
var elem = event.target;
var isDoc = elem === document;
var oldTop = targetData.top || 0;
var oldLeft = targetData.left || 0;
targetData.top = isDoc ? elem.documentElement.scrollTop + elem.body.scrollTop : elem.scrollTop;
targetData.left = isDoc ? elem.documentElement.scrollLeft + elem.body.scrollLeft : elem.scrollLeft;
event.scrollTopDelta = targetData.top - oldTop;
event.scrollTop = targetData.top;
event.scrollLeftDelta = targetData.left - oldLeft;
event.scrollLeft = targetData.left;
event.type = handleObj.origType;
ret = handleObj.handler.apply(this, arguments);
event.type = handleObj.type;
return ret;
}
};
// bind to custom 'scrolldelta' event
$(window).on('scrolldelta', function (e) {
var top = e.scrollTop;
var topDelta = e.scrollTopDelta;
var left = e.scrollLeft;
var leftDelta = e.scrollLeftDelta;
// do stuff with the above info; for now just display it to user
var feedbackText = 'scrollTop: ' + top.toString() + 'px (' + (topDelta >= 0 ? '+' : '') + topDelta.toString() + 'px), scrollLeft: ' + left.toString() + 'px (' + (leftDelta >= 0 ? '+' : '') + leftDelta.toString() + 'px)';
document.getElementById('feedback').innerHTML = feedbackText;
});
#content {
/* make window tall enough for vertical scroll */
height: 2000px;
/* make window wide enough for horizontal scroll */
width: 2000px;
/* visualization of scrollable content */
background-color: blue;
}
#feedback {
border:2px solid red;
padding: 4px;
color: black;
position: fixed;
top: 0;
height: 20px;
background-color: #fff;
font-family:'Segoe UI', 'Arial';
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='feedback'>scrollTop: 0px, scrollLeft: 0px</div>
<div id='content'></div>
Note that you may want debounce the event depending on what you are doing. You didn't provide very much context in your question, but if you give a better example of what you are actually using this info for we can provide a better answer. (Please show more of your code, and how you are using the "scroll value").
To detemine how many pixels were scrolled you have to keep in mind that the scroll event gets fired almost every pixel that you move. The way to accomplish it is to save the previous scrolled value and compare that in a timeout. Like this:
var scrollValue = 0;
var scrollTimeout = false
$(window).scroll(function(event){
/* Clear it so the function only triggers when scroll events have stopped firing*/
clearTimeout(scrollTimeout);
/* Set it so it fires after a second, but gets cleared after a new triggered event*/
scrollTimeout = setTimeout(function(){
var scrolled = $(document).scrollTop() - scrollValue;
scrollValue = $(document).scrollTop();
alert("The value scrolled was " + scrolled);
}, 1000);
});
This way you will get the amount of scrolled a second after scrolling (this is adjustable but you have to keep in mind that the smooth scrolling that is so prevalent today has some run-out time and you dont want to trigger before a full stop).
The other way to do this? Yes, possible, with jQuery Mobile
I do not appreciate this solution, because it is necessary to include heavy jQuery mobile. Solution:
var diff, top = 0;
$(document).on("scrollstart",function () {
// event fired when scrolling is started
top = $(window).scrollTop();
});
$(document).on("scrollstop",function () {
// event fired when scrolling is stopped
diff = Math.abs($(window).scrollTop() - top);
});
To reduce the used processing power by adding a timer to a Jquery scroll method is probably not a great idea. The visual effect is indeed quite bad.
The whole web browsing experience could be made much better by hiding the scrolling element just when the scroll begins and making it slide in (at the right position) some time after. The scrolling even can be checked with a delay too.
This solution works great.
$(document).ready(function() {
var element = $('.movable_div'),
originalY = element.offset().top;
element.css('position', 'relative');
$(window).on('scroll', function(event) {
var scrollTop = $(window).scrollTop();
element.hide();
element.stop(false, false).animate({
top: scrollTop < originalY
? 0
: scrollTop - originalY + 35
}, 2000,function(){element.slideDown(500,"swing");});
});
});
Live demo here

Categories

Resources