我有一段定期执行的JavaScript代码。当用户不在浏览网站时(即窗口或选项卡没有焦点),最好不要运行它。
是否有一种使用JavaScript实现此功能的方法?
我的参考对象是 Gmail 聊天,如果你正在使用的窗口不活跃,它会播放声音。
我有一段定期执行的JavaScript代码。当用户不在浏览网站时(即窗口或选项卡没有焦点),最好不要运行它。
是否有一种使用JavaScript实现此功能的方法?
我的参考对象是 Gmail 聊天,如果你正在使用的窗口不活跃,它会播放声音。
自本回答最初发布以来,得益于W3C,一项新的标准已经达到了推荐状态。 页面可见性API(在MDN上)现在允许我们更准确地检测页面何时对用户隐藏。
document.addEventListener("visibilitychange", onchange);
当前浏览器支持情况:
在不兼容的浏览器中,以下代码会回退到不太可靠的blur/focus方法:
(function() {
var hidden = "hidden";
// Standards:
if (hidden in document)
document.addEventListener("visibilitychange", onchange);
else if ((hidden = "mozHidden") in document)
document.addEventListener("mozvisibilitychange", onchange);
else if ((hidden = "webkitHidden") in document)
document.addEventListener("webkitvisibilitychange", onchange);
else if ((hidden = "msHidden") in document)
document.addEventListener("msvisibilitychange", onchange);
// IE 9 and lower:
else if ("onfocusin" in document)
document.onfocusin = document.onfocusout = onchange;
// All others:
else
window.onpageshow = window.onpagehide
= window.onfocus = window.onblur = onchange;
function onchange (evt) {
var v = "visible", h = "hidden",
evtMap = {
focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h
};
evt = evt || window.event;
if (evt.type in evtMap)
document.body.className = evtMap[evt.type];
else
document.body.className = this[hidden] ? "hidden" : "visible";
}
// set the initial state (but only if browser supports the Page Visibility API)
if( document[hidden] !== undefined )
onchange({type: document[hidden] ? "blur" : "focus"});
})();
onfocusin
和onfocusout
在IE 9及以下版本中必需,而其他所有浏览器都使用onfocus
和onblur
,iOS使用onpageshow
和onpagehide
。
focusin
和 focusout
从 iframe 传递到上层窗口。对于较新的浏览器,您只需要在每个 iframe 的 window
对象上处理 focus
和 blur
事件即可。您应该使用我刚添加的更新代码,它至少可以覆盖新浏览器中的这些情况。 - Andy E我会使用 jQuery,因为你只需要这样做:
$(window).blur(function(){
//your code here
});
$(window).focus(function(){
//your code
});
或者至少对我来说有效。
有三种常用方法用于确定用户是否能看到HTML页面,但它们都不完美:
W3C页面可见性API被认为可以实现这一点(支持自Firefox 10、MSIE 10和Chrome 13)。然而,该API仅在浏览器选项卡完全覆盖时引发事件(例如,当用户从一个选项卡切换到另一个选项卡时)。该API在无法以100%的准确度确定可见性时(例如,使用Alt+Tab切换到另一个应用程序)不会引发事件。
使用基于焦点/失焦的方法会给出很多误报。例如,如果用户在浏览器窗口上方显示了一个较小的窗口,则浏览器窗口将丢失焦点(onblur
被触发),但用户仍然能够看到它(因此仍需要刷新)。请参阅http://javascript.info/tutorial/focus
依赖用户活动(鼠标移动、点击、键入)也会给出很多误报。想象一下与上述情况相同的情况,或者用户观看视频。
为了改善上述不完美的行为,我使用三种方法的组合:W3C可见性API,然后是焦点/失焦和用户活动方法,以减少误报率。这允许管理以下事件:
它的工作原理如下:当文档失去焦点时,监视文档上的用户活动(例如鼠标移动),以确定窗口是否可见。页面可见性概率与页面上最后一次用户活动的时间成反比:如果用户长时间未对文档进行任何活动,则该页面很可能不可见。下面的代码模仿W3C页面可见性API:它的行为方式相同,但具有较小的误报率。它的优点是跨浏览器(已在Firefox 5、Firefox 10、MSIE 9、MSIE 7、Safari 5、Chrome 9上测试)。
<div id="x"></div>
<script>
/**
Registers the handler to the event for the given object.
@param obj the object which will raise the event
@param evType the event type: click, keypress, mouseover, ...
@param fn the event handler function
@param isCapturing set the event mode (true = capturing event, false = bubbling event)
@return true if the event handler has been attached correctly
*/
function addEvent(obj, evType, fn, isCapturing){
if (isCapturing==null) isCapturing=false;
if (obj.addEventListener){
// Firefox
obj.addEventListener(evType, fn, isCapturing);
return true;
} else if (obj.attachEvent){
// MSIE
var r = obj.attachEvent('on'+evType, fn);
return r;
} else {
return false;
}
}
// register to the potential page visibility change
addEvent(document, "potentialvisilitychange", function(event) {
document.getElementById("x").innerHTML+="potentialVisilityChange: potentialHidden="+document.potentialHidden+", document.potentiallyHiddenSince="+document.potentiallyHiddenSince+" s<br>";
});
// register to the W3C Page Visibility API
var hidden=null;
var visibilityChange=null;
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") {
hidden="hidden";
visibilityChange="visibilitychange";
}
if (hidden!=null && visibilityChange!=null) {
addEvent(document, visibilityChange, function(event) {
document.getElementById("x").innerHTML+=visibilityChange+": "+hidden+"="+document[hidden]+"<br>";
});
}
var potentialPageVisibility = {
pageVisibilityChangeThreshold:3*3600, // in seconds
init:function() {
function setAsNotHidden() {
var dispatchEventRequired=document.potentialHidden;
document.potentialHidden=false;
document.potentiallyHiddenSince=0;
if (dispatchEventRequired) dispatchPageVisibilityChangeEvent();
}
function initPotentiallyHiddenDetection() {
if (!hasFocusLocal) {
// the window does not has the focus => check for user activity in the window
lastActionDate=new Date();
if (timeoutHandler!=null) {
clearTimeout(timeoutHandler);
}
timeoutHandler = setTimeout(checkPageVisibility, potentialPageVisibility.pageVisibilityChangeThreshold*1000+100); // +100 ms to avoid rounding issues under Firefox
}
}
function dispatchPageVisibilityChangeEvent() {
unifiedVisilityChangeEventDispatchAllowed=false;
var evt = document.createEvent("Event");
evt.initEvent("potentialvisilitychange", true, true);
document.dispatchEvent(evt);
}
function checkPageVisibility() {
var potentialHiddenDuration=(hasFocusLocal || lastActionDate==null?0:Math.floor((new Date().getTime()-lastActionDate.getTime())/1000));
document.potentiallyHiddenSince=potentialHiddenDuration;
if (potentialHiddenDuration>=potentialPageVisibility.pageVisibilityChangeThreshold && !document.potentialHidden) {
// page visibility change threshold raiched => raise the even
document.potentialHidden=true;
dispatchPageVisibilityChangeEvent();
}
}
var lastActionDate=null;
var hasFocusLocal=true;
var hasMouseOver=true;
document.potentialHidden=false;
document.potentiallyHiddenSince=0;
var timeoutHandler = null;
addEvent(document, "pageshow", function(event) {
document.getElementById("x").innerHTML+="pageshow/doc:<br>";
});
addEvent(document, "pagehide", function(event) {
document.getElementById("x").innerHTML+="pagehide/doc:<br>";
});
addEvent(window, "pageshow", function(event) {
document.getElementById("x").innerHTML+="pageshow/win:<br>"; // raised when the page first shows
});
addEvent(window, "pagehide", function(event) {
document.getElementById("x").innerHTML+="pagehide/win:<br>"; // not raised
});
addEvent(document, "mousemove", function(event) {
lastActionDate=new Date();
});
addEvent(document, "mouseover", function(event) {
hasMouseOver=true;
setAsNotHidden();
});
addEvent(document, "mouseout", function(event) {
hasMouseOver=false;
initPotentiallyHiddenDetection();
});
addEvent(window, "blur", function(event) {
hasFocusLocal=false;
initPotentiallyHiddenDetection();
});
addEvent(window, "focus", function(event) {
hasFocusLocal=true;
setAsNotHidden();
});
setAsNotHidden();
}
}
potentialPageVisibility.pageVisibilityChangeThreshold=4; // 4 seconds for testing
potentialPageVisibility.init();
</script>
由于目前没有可靠的跨浏览器解决方案且会有误报,因此您最好再三考虑是否禁用网站上的定期活动。
使用:页面可见性 API
document.addEventListener( 'visibilitychange' , function() {
if (document.hidden) {
console.log('bye');
} else {
console.log('well back');
}
}, false );
起初,我使用了社区维基的答案,但是发现它无法检测到Chrome中的alt-tab事件。这是因为它使用了第一个可用的事件源,在这种情况下,它是页面可见性API,在Chrome中似乎无法跟踪alt-tab。
我决定稍微修改一下脚本,以跟踪所有可能的页面焦点变化事件。这里是一个可以插入的函数:
function onVisibilityChange(callback) {
var visible = true;
if (!callback) {
throw new Error('no callback given');
}
function focused() {
if (!visible) {
callback(visible = true);
}
}
function unfocused() {
if (visible) {
callback(visible = false);
}
}
// Standards:
if ('hidden' in document) {
visible = !document.hidden;
document.addEventListener('visibilitychange',
function() {(document.hidden ? unfocused : focused)()});
}
if ('mozHidden' in document) {
visible = !document.mozHidden;
document.addEventListener('mozvisibilitychange',
function() {(document.mozHidden ? unfocused : focused)()});
}
if ('webkitHidden' in document) {
visible = !document.webkitHidden;
document.addEventListener('webkitvisibilitychange',
function() {(document.webkitHidden ? unfocused : focused)()});
}
if ('msHidden' in document) {
visible = !document.msHidden;
document.addEventListener('msvisibilitychange',
function() {(document.msHidden ? unfocused : focused)()});
}
// IE 9 and lower:
if ('onfocusin' in document) {
document.onfocusin = focused;
document.onfocusout = unfocused;
}
// All others:
window.onpageshow = window.onfocus = focused;
window.onpagehide = window.onblur = unfocused;
};
像这样使用:
onVisibilityChange(function(visible) {
console.log('the page is now', visible ? 'focused' : 'unfocused');
});
这个版本监听所有不同的可见性事件,如果它们中的任何一个发生变化,则触发回调。 focused
和 unfocused
处理程序确保如果多个API捕获相同的可见性变化,则回调不会被多次调用。
document.hidden
和document.webkitHidden
。如果在if
语句中没有else
,我们会得到2个回调调用,对吧? - Christiaan Westerbeek在GitHub上有一个很棒的库:
https://github.com/serkanyersen/ifvisible.js
示例:
// If page is visible right now
if( ifvisible.now() ){
// Display pop-up
openPopUp();
}
我已在所有我拥有的浏览器上测试了1.0.1版本,并确认它可以与以下浏览器配合使用:
......可能也适用于所有更新版本的浏览器。
但是与以下浏览器配合使用时存在问题:
.now()
始终返回true
)我为我的应用程序创建了Comet Chat,当我从另一个用户那里收到消息时,我使用:
if(new_message){
if(!document.hasFocus()){
audio.play();
document.title="Have new messages";
}
else{
audio.stop();
document.title="Application Name";
}
}
document.hasFocus()
是最简洁的方法。所有其他使用可见性API或事件驱动或寻找各种用户活动/不活动级别的方式都变得过于复杂,充满了边缘情况和漏洞。将其放在简单的时间间隔上,并在结果更改时引发自定义事件。示例:https://jsfiddle.net/59utucz6/1/ - danatcofosetInterval(() => { console.log(document.hasFocus()) }, 1000);
- Walter Stabosz这适用于所有现代浏览器:
var eventName;
var visible = true;
var propName = "hidden";
if (propName in document) eventName = "visibilitychange";
else if ((propName = "msHidden") in document) eventName = "msvisibilitychange";
else if ((propName = "mozHidden") in document) eventName = "mozvisibilitychange";
else if ((propName = "webkitHidden") in document) eventName = "webkitvisibilitychange";
if (eventName) document.addEventListener(eventName, handleChange);
if ("onfocusin" in document) document.onfocusin = document.onfocusout = handleChange; //IE 9
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = handleChange;// Changing tab with alt+tab
// Initialize state if Page Visibility API is supported
if (document[propName] !== undefined) handleChange({ type: document[propName] ? "blur" : "focus" });
function handleChange(evt) {
evt = evt || window.event;
if (visible && (["blur", "focusout", "pagehide"].includes(evt.type) || (this && this[propName]))){
visible = false;
console.log("Out...")
}
else if (!visible && (["focus", "focusin", "pageshow"].includes(evt.type) || (this && !this[propName]))){
visible = true;
console.log("In...")
}
}
https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test2.html
我还没有在足够多的浏览器上测试过这个。 如果你能找到更多关于它不能工作的信息,请在下面的评论中让我知道。
var visibilityChange = (function (window) {
var inView = false;
return function (fn) {
window.onfocus = window.onblur = window.onpageshow = window.onpagehide = function (e) {
if ({focus:1, pageshow:1}[e.type]) {
if (inView) return;
fn("visible");
inView = true;
} else if (inView) {
fn("hidden");
inView = false;
}
};
};
}(this));
visibilityChange(function (state) {
console.log(state);
});
requestAnimationFrame
API,或使用现代特性,即当窗口不可见时setTimeout
/setInterval
的频率会降低(例如在 Chrome 中约为1秒)。请注意,翻译尽可能保持原意,简化表达,并避免添加解释。 - Rob Wblur
/focus
。 - Mathias Bynensblur
/focus
事件...尽管如此,这将具有有限的用途,因为窗口可以是非活动的但完全或部分可见(某些任务栏中也有“预览”图标,人们希望继续更新)。 - rustyx