Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Created August 3, 2011 12:44
Show Gist options
  • Star 63 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save addyosmani/1122546 to your computer and use it in GitHub Desktop.
Save addyosmani/1122546 to your computer and use it in GitHub Desktop.
Cross-browser Page Visibility API polyfill
/*!
* isVis - v0.5.5 Aug 2011 - Page Visibility API Polyfill
* Copyright (c) 2011 Addy Osmani
* Dual licensed under the MIT and GPL licenses.
*/
(function () {
window.visibly = {
b: null,
q: document,
p: undefined,
prefixes: ['webkit', 'ms'],
props: ['VisibilityState', 'visibilitychange', 'Hidden'],
m: ['focus', 'blur'],
visibleCallbacks: [],
hiddenCallbacks: [],
_callbacks: [],
onVisible: function ( _callback ) {
this.visibleCallbacks.push(_callback);
},
onHidden: function ( _callback ) {
this.hiddenCallbacks.push(_callback);
},
isSupported: function () {
return (this._supports(0) || this._supports(1));
},
_supports: function ( index ) {
return ((this.prefixes[index] + this.props[2]) in this.q);
},
runCallbacks: function ( index ) {
if ( index ) {
this._callbacks = (index == 1) ? this.visibleCallbacks : this.hiddenCallbacks;
for (var i = 0; i < this._callbacks.length; i++) {
this._callbacks[i]();
}
}
},
_visible: function () {
window.visibly.runCallbacks(1);
},
_hidden: function () {
window.visibly.runCallbacks(2);
},
_nativeSwitch: function () {
((this.q[this.b + this.props[2]]) === true) ? this._hidden() : this._visible();
},
listen: function () {
try { /*if no native page visibility support found..*/
if (!(this.isSupported())) {
if (document.addEventListener) { /*for browsers without focusin/out support eg. firefox, opera use focus/blur*/
/*window used instead of doc as Opera complains otherwise*/
window.addEventListener(this.m[0], this._visible, 1);
window.addEventListener(this.m[1], this._hidden, 1);
} else { /*IE <10s most reliable focus events are onfocusin/onfocusout*/
this.q.attachEvent('onfocusin', this._visible);
this.q.attachEvent('onfocusout', this._hidden);
}
} else { /*switch support based on prefix*/
this.b = (this._supports(0) == this.p) ? this.prefixes[1] : this.prefixes[0];
this.q.addEventListener(this.b + this.props[1], function () {
window.visibly._nativeSwitch.apply(window.visibly, arguments);
}, 1);
}
} catch (e) {}
},
init: function () {
this.listen();
}
}
this.visibly.init();
})();
/*usage**/
visibly.onVisible(function () {
document.title = 'visible'
});
visibly.onVisible(function () {
console.log('visible');
});
visibly.onHidden(function () {
document.title = 'hidden';
});
visibly.onHidden(function () {
console.log('hidden');
});
@addyosmani
Copy link
Author

Tested cross-browser and it should hopefully work for anyone trying it. Going to focus on making it more DRY.

@addyosmani
Copy link
Author

Tested across: Chromium nightly, Chrome 12, IE10PP, IE9, IE8, Opera 9.5, Firefox 5.

@zachleat
Copy link

zachleat commented Aug 3, 2011

What if I already have a onfocusin/onfocusout event bound?

@addyosmani
Copy link
Author

@zachleat, I believe the latest update should resolve those issues. I've also included a quick test above the isVis() usage examples which when executed along with the rest of the code ensures that a, b, q and k are output based on your interaction with the tabs with the document title changes also updating as expected.

@edwardbc
Copy link

Interesting, when activating the tab in Chrome 24 , the line document.title = 'visible' won't work immedately, but it works if I wrap this in a setTimeout.

@adamwaite
Copy link

Shame it doesn't work if you suspend mobile safari and then resume it (iOS)

@adamwaite
Copy link

Ah turns out you can:

window.addEventListener("pagehide", function(evt){
$('p#notice').html('CALLED');
}, false);

@loicginoux
Copy link

It's working on my Safari 6.0.5, has anyone tried on Safari 5?

@Swaagie
Copy link

Swaagie commented Aug 13, 2013

Note that firefox also has a native API, so this polyfill is only partial correct. Changed one can be found as fork
https://gist.github.com/Swaagie/6221897

@mircobabini
Copy link

First of all i forked from the @Swaagie fork because it supports natively firefox. After that i added two methods: isVisible and isHidden. Usage example:

setInterval( function (){

  // some jquery effects on elements here
  // if the page isn't on focus, the effects accumulate on themselves and run once the page gets back to focus

}, 1000);

So, it's better if you can check the visibility into the lambda func, wrapping the jquery effects into an isVisible test!
Reference: https://gist.github.com/mircobabini/3812165023fd671745ab

@hexalys
Copy link

hexalys commented Jun 20, 2014

@zachleat
Honestly, IMO it can hardly be called a polyfill, as it doesn't fully reproduce the native Visibility behavior.

onblur and onfocus check the document's active state, which is still relevant and has its use today, separately from the Page Visibility API.

It is only relevant and a true polyfill in (non snapmode/split screen) mobile environments. Elsewhere, I am afraid it gives the wrong signal.

@clemorphy
Copy link

Works fine on Nexus5 with Chrome but fails on iPad3 iOS7 with Safari and Chrome ...

@AlbertoFdzM
Copy link

AlbertoFdzM commented Oct 10, 2017

If someone gets here for some reason like myself trying to find a good polyfill of document.hidden and the Page Visibility API, mind that this API is now well supported by browsers.

You could use this cool library too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment