The Page Visibility API tries to solve a simple problem: is the current document visible to the user? It is a common issue that is raised in environments where a user may have a document open, but where it is might not be seen by a user because it is ‘hidden’ by being either minimized or covered by another window. The API exists as a way for a page to have this information itself and act accordingly, either stopping computationally intensive code or saving data while the user is looking away.
Like most HTML5 functionality, the Page Visibility API has good cross-browser support (~66%), but currently uses vendor prefixes. Most major browsers also implement a majority of the specification, but with the caveat that some do not return the optional statues of “prerender” (loaded but not yet shown) and “unloaded” (not shown and is being removed from memory). However, the primary “hidden” or “visible” are well supported.
Testing for the Page Visibility API in the current browser context consists of, at the time of this writing, going through a list of vendor prefixed ‘hidden’ property of the document object. If any are found, support is assumed.
var Visibility = (function(self) { | |
self.supported = !!document.webkitHidden || | |
!!document.mozHidden || | |
!!document.msHidden || | |
!!document.oHidden || | |
!!document.hidden; | |
... | |
})(Visibility || {}); |
However, just because the Page Visibility API is not directly supported doesn’t mean some of its functionality cannot be emulated. By registering event listeners with the “blur” and “focus” events of the document, the ability to test for a user interacting with a document (focus) or is covered (blur) can mimic parts of the Page Visibility API in those browser contexts that are older or do not have full support yet.
/** | |
* A Visibility object using the Page Visibility API with fallback to | |
* the the 'focus' and 'blur' events. | |
* | |
* The result of visibilityState() returns one of the possible following strings: | |
* "visible" : is visible to the user | |
* "hidden" : is not visible to the user | |
* "prerender" : (Limited support) is not visible to the user, but has been loaded in memory | |
* "unloaded" : (Limited support) the page itself is currently being unloaded from memory | |
* | |
* @property {boolean} supported If the Page Visibility API is supported in this context or not | |
* | |
*/ | |
var Visibility = (function(self) { | |
self.supported = !!document.webkitHidden || | |
!!document.mozHidden || | |
!!document.msHidden || | |
!!document.oHidden || | |
!!document.hidden; | |
var visible = null; | |
var callback = null; | |
var hidden = true; | |
/** | |
* If the entire page is hidden from the user or not | |
* @returns {boolean} The hidden status of the page | |
*/ | |
self.isHidden = function() { | |
if (!!document.webkitHidden) { | |
hidden = document.webkitHidden; | |
} else if (!!document.mozHidden) { | |
hidden = document.mozHidden; | |
} else if (!!document.msHidden) { | |
hidden = document.msHidden; | |
} else if (!!document.oHidden) { | |
hidden = document.oHidden; | |
} else if (!!document.hidden) { | |
hidden = document.hidden; | |
} | |
return hidden; | |
}; | |
/** | |
* Returns the visibility state of the page | |
* @returns {string} Either "visible", "hidden", "prerender", or "unloaded" | |
*/ | |
self.visibilityState = function() { | |
if (self.supported) { | |
if (document.webkitVisibilityState) { | |
return document.webkitVisibilityState; | |
} else { | |
return document.visibilityState; | |
} | |
} else if (visible !== null) { | |
return visible; | |
} else { | |
return "prerender"; | |
} | |
}; | |
/** | |
* Add a Visibility EventListener callback function | |
* @param {function} callbackfunc The function to be called when the visibility changes | |
*/ | |
self.addVisibilityListener = function(callbackfunc) { | |
if (self.supported) { | |
callback = callbackfunc; | |
var visibilityChange = null; | |
if (!!document.hidden) { | |
visibilityChange = "visibilitychange"; | |
} else if (!!document.mozHidden) { | |
visibilityChange = "mozvisibilitychange"; | |
} else if (!!document.msHidden) { | |
visibilityChange = "msvisibilitychange"; | |
} else if (!!document.webkitHidden) { | |
visibilityChange = "webkitvisibilitychange"; | |
} | |
if (visibilityChange !== null) { | |
document.addEventListener(visibilityChange, callback, false); | |
} | |
} else { | |
if (document.attachEvent) { | |
document.attachEvent('onfocusin', onPageFocus); | |
document.attachEvent('onfocusout', onPageBlur); | |
} else { | |
document.addEventListener('focus', onPageFocus); | |
document.addEventListener('blur', onPageBlur); | |
} | |
} | |
}; | |
/* | |
* The onFocus event function for the document | |
*/ | |
function onPageFocus(event) { | |
hidden = false; | |
visible = "visible"; | |
if (callback !== null) { | |
callback(event); | |
} | |
} | |
/* | |
* The onBlur event function for the document | |
*/ | |
function onPageBlur(event) { | |
hidden = true; | |
visible = "hidden"; | |
if (callback !== null) { | |
callback(event); | |
} | |
} | |
return self; | |
})(Visibility || {}); |