有 3 种典型的方法用于确定用户是否可以看到 HTML 页面,但是它们都不能完美地工作:
W3C Page Visibility API 应该执行此操作(支持自:Firefox 10、MSIE 10、Chrome 13)。但是,此 API 仅在完全覆盖浏览器选项卡时引发事件(例如,当用户从一个选项卡更改为另一个选项卡时)。当无法以 100% 的准确度确定可见性时,API 不会引发事件(例如,Alt+Tab 切换到另一个应用程序)。
使用基于焦点/模糊的方法会给您带来很多误报。例如,如果用户在浏览器窗口顶部显示一个较小的窗口,浏览器窗口将失去焦点(onblur 升起)但用户仍然可以看到它(因此仍需要刷新)。另见http://javascript.info/tutorial/focus
-
依赖用户活动(鼠标移动、点击、键入的键)也会给您带来很多误报。考虑与上述相同的情况,或者用户正在观看视频。
为了改善上述不完美的行为,我使用了 3 种方法的组合:W3C Visibility API,然后是 focus/blur 和用户活动方法,以降低误报率。这允许管理以下事件:
- 将浏览器选项卡更改为另一个(100% 准确,感谢 W3C 页面可见性 API)
- 页面可能被另一个窗口隐藏,例如由于 Alt+Tab(概率 = 不是 100% 准确)
- 用户的注意力可能没有集中在 HTML 页面上(概率 = 不是 100% 准确)
它是这样工作的:当文档失去焦点时,会监视文档上的用户活动(例如鼠标移动)以确定窗口是否可见。页面可见概率与页面上最后一次用户活动的时间成反比:如果用户长时间没有在文档上进行任何活动,则该页面很可能是不可见的。下面的代码模仿了 W3C Page Visibility API:它的行为方式相同,但误报率很小。它具有多浏览器的优势(在 Firefox 5、Firefox 10、MSIE 9、MSIE 7、Safari 5、Chrome 9 上测试)。
/**
将处理程序注册到给定对象的事件。
@param obj 将引发事件的对象
@param evType 事件类型:单击,按键,鼠标悬停,...
@param fn 事件处理函数
@param isCapturing 设置事件模式(true = 捕获事件,false = 冒泡事件)
@return 如果事件处理程序已正确附加,则返回 true
*/
函数 addEvent(obj, evType, fn, isCapturing){
if (isCapturing==null) isCapturing=false;
如果(obj.addEventListener){
// 火狐
obj.addEventListener(evType, fn, isCapturing);
返回真;
} 否则如果(obj.attachEvent){
// MSIE
var r = obj.attachEvent('on'+evType, fn);
返回 r;
} 别的 {
返回假;
}
}
// 注册到潜在的页面可见性变化
addEvent(document, "potentialvisilitychange", function(event) {
document.getElementById("x").innerHTML+="potentialVisilityChange: potentialHidden="+document.potentialHidden+", document.potentiallyHiddenSince="+document.potentiallyHiddenSince+" s
";
});
// 注册到 W3C Page Visibility API
变种隐藏=空;
变量可见性更改=空;
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";
} else if (typeof document.hidden !=="hidden") {
隐藏=“隐藏”;
可见性改变=“可见性改变”;
}
if (hidden!=null && visibilityChange!=null) {
addEvent(document, visibilityChange, function(event) {
document.getElementById("x").innerHTML+=visibilityChange+": "+hidden+"="+document[hidden]+"
";
});
}
var potentialPageVisibility = {
pageVisibilityChangeThreshold:3*3600, // 以秒为单位
初始化:函数(){
函数 setAsNotHidden() {
var dispatchEventRequired=document.potentialHidden;
document.potentialHidden=false;
document.potentiallyHiddenSince=0;
if (dispatchEventRequired) dispatchPageVisibilityChangeEvent();
}
函数 initPotentiallyHiddenDetection() {
如果(!hasFocusLocal){
// 窗口没有焦点 => 检查窗口中的用户活动
lastActionDate=新日期();
if (timeoutHandler!=null) {
清除超时(超时处理程序);
}
timeoutHandler = setTimeout(checkPageVisibility, potentialPageVisibility.pageVisibilityChangeThreshold*1000+100); // +100 ms 以避免 Firefox 下的舍入问题
}
}
函数 dispatchPageVisibilityChangeEvent() {
统一的VisilityChangeEventDispatchAllowed=false;
var evt = document.createEvent("事件");
evt.initEvent("potentialvisilitychange", true, true);
document.dispatchEvent(evt);
}
函数 checkPageVisibility() {
var potentialHiddenDuration=(hasFocusLocal || lastActionDate==null?0:Math.floor((new Date().getTime()-lastActionDate.getTime())/1000));
document.potentiallyHiddenSince=potentialHiddenDuration;
if (potentialHiddenDuration>=potentialPageVisibility.pageVisibilityChangeThreshold && !document.potentialHidden) {
// 页面可见性更改阈值达到 => 提高偶数
document.potentialHidden=true;
dispatchPageVisibilityChangeEvent();
}
}
var lastActionDate=null;
var hasFocusLocal=true;
变量 hasMouseOver=true;
document.potentialHidden=false;
document.potentiallyHiddenSince=0;
var timeoutHandler = null;
addEvent(document, "pageshow", function(event) {
document.getElementById("x").innerHTML+="pageshow/doc:
";
});
addEvent(document, "pagehide", function(event) {
document.getElementById("x").innerHTML+="pagehide/doc:
";
});
addEvent(窗口,“页面显示”,函数(事件){
document.getElementById("x").innerHTML+="pageshow/win:
"; // 在页面首次显示时引发
});
addEvent(窗口,“页面隐藏”,函数(事件){
document.getElementById("x").innerHTML+="pagehide/win:
"; // 未提出
});
addEvent(文档,“mousemove”,函数(事件){
lastActionDate=新日期();
});
addEvent(文档,“鼠标悬停”,函数(事件){
hasMouseOver=真;
setAsNotHidden();
});
addEvent(文档,“mouseout”,函数(事件){
hasMouseOver=假;
initPotentiallyHiddenDetection();
});
addEvent(窗口,“模糊”,函数(事件){
hasFocusLocal=false;
initPotentiallyHiddenDetection();
});
addEvent(窗口,“焦点”,函数(事件){
hasFocusLocal=true;
setAsNotHidden();
});
setAsNotHidden();
}
}
潜在的PageVisibility.pageVisibilityChangeThreshold=4; // 4 秒测试
潜在的PageVisibility.init();
脚本>
由于目前没有不存在误报的有效跨浏览器解决方案,因此在禁用网站上的定期活动时,您最好三思而后行。