我的JavaScript会定期进行活动。当用户不查看站点时(即窗口或选项卡没有焦点),最好不要运行。
有没有办法使用JavaScript做到这一点?
我的参考点:如果您使用的窗口未激活,则Gmail聊天会播放声音。
我的JavaScript会定期进行活动。当用户不查看站点时(即窗口或选项卡没有焦点),最好不要运行。
有没有办法使用JavaScript做到这一点?
我的参考点:如果您使用的窗口未激活,则Gmail聊天会播放声音。
Answers:
自从最初编写此答案以来,由于有了W3C ,新的规范已达到推荐状态。现在,页面可见性API(在MDN上)使我们能够更准确地检测页面何时向用户隐藏。
document.addEventListener("visibilitychange", onchange);
当前的浏览器支持:
以下代码会退回到不兼容浏览器中不太可靠的模糊/聚焦方法:
(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
和IE 9及更低版本onfocusout
是必需的,而其他所有语言都使用onfocus
和onblur
(iOS除外,后者使用onpageshow
和)onpagehide
。
focusin
并focusout
从的IFRAME到上部窗口。对于较新的浏览器,您只需要处理每个iframe 对象上的focus
和blur
事件window
。您应该使用我刚刚添加的更新代码,这至少会涵盖较新浏览器中的情况。
我将使用jQuery,因为那么您要做的就是:
$(window).blur(function(){
//your code here
});
$(window).focus(function(){
//your code
});
或者至少对我有用。
window
将失去焦点,这是正确的,但是取决于您的意图可能不是您的需要。
有3种典型方法可用来确定用户是否可以看到HTML页面,但是它们都不完美:
在W3C网页浏览权限API应该做到这一点(支持,因为:火狐10,MSIE 10,铬13)。但是,仅当完全覆盖浏览器选项卡时(例如,当用户从一个选项卡更改为另一个选项卡时),此API才会引发事件。当无法以100%的精度确定可见性时,API不会引发事件(例如,使用Alt + Tab切换到另一个应用程序)。
使用基于焦点/模糊的方法会给您带来很多误报。例如,如果用户在浏览器窗口上方显示一个较小的窗口,则浏览器窗口将失去焦点(onblur
升起),但用户仍然可以看到它(因此仍需要刷新)。另请参见http://javascript.info/tutorial/focus
为了改善上述不完美的行为,我使用了以下3种方法的组合:W3C Visibility API,然后是focus / blur和user activity方法,以减少误报率。这允许管理以下事件:
它是这样工作的:当文档失去焦点时,将监视文档上的用户活动(例如鼠标移动),以确定该窗口是否可见。页面可见性概率与页面上最后一次用户活动的时间成反比:如果用户长时间未在文档上进行任何活动,则该页面很可能不可见。下面的代码模仿W3C页面可见性API:行为相同,但误报率小。它具有多浏览器的优势(已在Firefox 5,Firefox 10,MSIE 9,MSIE 7,Safari 5,Chrome 9上测试)。
<div id =“ x”> </ div> <脚本> / ** 将处理程序注册到给定对象的事件。 @param obj将引发事件的对象 @param evType事件类型:单击,按键,鼠标悬停,... @param fn事件处理函数 @param isCapturing设置事件模式(true =捕获事件,false =冒泡事件) @return true,如果事件处理程序已正确附加 * / 函数addEvent(obj,evType,fn,isCapturing){ 如果(isCapturing == null)isCapturing = false; 如果(obj.addEventListener){ // Firefox obj.addEventListener(evType,fn,isCapturing); 返回true; }否则,如果(obj.attachEvent){ // MSIE var r = obj.attachEvent('on'+ evType,fn); 返回r; }其他{ 返回false; } } //注册到潜在的页面可见性更改 addEvent(document,“ potentialvisilitychange”,function(event){ document.getElementById(“ x”)。innerHTML + =“ potentialVisilityChange:potentialHidden =” + document.potentialHidden +“,document.potentiallyHiddenSince =” + document.potentiallyHiddenSince +“ s <br>”; }); //注册到W3C页面可见性API var hidden = null; var visibleChange = null; 如果(typeof document.mozHidden!==“未定义”){ hidden =“ mozHidden”; ibilityChange =“ mozvisibilitychange”; }否则if(typeof document.msHidden!==“ undefined”){ hidden =“ msHidden”; ibilityChange =“ msvisibilitychange”; }否则,如果(typeof document.webkitHidden!==“ undefined”){ hidden =“ webkitHidden”; ibilityChange =“ webkitvisibilitychange”; } else if(typeof document.hidden!==“ hidden”){ hidden =“ hidden”; ibilityChange =“ visibilitychange”; } if(hidden!= null && visibleChange!= null){ addEvent(document,visibleibilityChange,function(event){ document.getElementById(“ x”)。innerHTML + = visibilityChange +“:” + hidden +“ =” + document [hidden] +“ <br>”; }); } var potentialPageVisibility = { pageVisibilityChangeThreshold:3 * 3600,//以秒为单位 init:function(){ 函数setAsNotHidden(){ var dispatchEventRequired = document.potentialHidden; document.potentialHidden = false; document.potentiallyHiddenSince = 0; 如果(dispatchEventRequired)dispatchPageVisibilityChangeEvent(); } 函数initPotentiallyHiddenDetection(){ 如果(!hasFocusLocal){ //窗口没有焦点=>检查窗口中的用户活动 lastActionDate = new Date(); 如果(timeoutHandler!= null){ clearTimeout(timeoutHandler); } timeoutHandler = setTimeout(checkPageVisibility,potentialPageVisibility.pageVisibilityChangeThreshold * 1000 + 100); // +100 ms以避免Firefox下的舍入问题 } } 函数dispatchPageVisibilityChangeEvent(){ UnifiedVisilityChangeEventDispatchAllowed = false; var evt = document.createEvent(“ Event”); 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; 如果(potentialHiddenDuration> = potentialPageVisibility.pageVisibilityChangeThreshold &&!document.potentialHidden){ //提升网页的可见度更改阈值=>提高均匀度 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>”; //页面首次显示时引发 }); addEvent(window,“ pagehide”,function(event){ document.getElementById(“ x”)。innerHTML + =“ pagehide / win:<br>”; //没有提出 }); 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秒进行测试 potentialPageVisibility.init(); </ script>
由于当前没有没有误报的跨浏览器解决方案,因此您最好对禁用网站上的定期活动进行三思。
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
为我返回)使用: 页面可见性API
document.addEventListener( 'visibilitychange' , function() {
if (document.hidden) {
console.log('bye');
} else {
console.log('well back');
}
}, false );
我为我的应用创建了彗星聊天,当我收到其他用户的消息时,我使用:
if(new_message){
if(!document.hasFocus()){
audio.play();
document.title="Have new messages";
}
else{
audio.stop();
document.title="Application Name";
}
}
document.hasFocus()
是最干净的方法。使用基于可见性api或事件或查找各种级别的用户活动/缺乏活动的所有其他方式变得过于复杂,并且充满了边缘情况和漏洞。将其放在一个简单的时间间隔上,并在结果更改时引发自定义事件。示例:jsfiddle.net/59utucz6/1
我开始使用社区Wiki答案,但意识到它没有在Chrome中检测到alt-tab事件。这是因为它使用了第一个可用的事件源,在这种情况下,它是页面可见性API,在Chrome中似乎不跟踪alt标签。
我决定稍微修改一下脚本,以跟踪页面焦点更改的所有可能事件。您可以使用以下功能:
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) {
document.addEventListener('visibilitychange',
function() {(document.hidden ? unfocused : focused)()});
}
if ('mozHidden' in document) {
document.addEventListener('mozvisibilitychange',
function() {(document.mozHidden ? unfocused : focused)()});
}
if ('webkitHidden' in document) {
document.addEventListener('webkitvisibilitychange',
function() {(document.webkitHidden ? unfocused : focused)()});
}
if ('msHidden' in document) {
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
。没有else
在if
构造中,我们将得到2个回调调用吗?
这确实很棘手。鉴于以下要求,似乎没有解决方案。
发生这种情况是因为:
鉴于这些限制,有可能实现一个解决方案,该解决方案将以下各项结合在一起:-页面可见性API-窗口模糊/焦点-document.activeElement
能够:
当iframe具有焦点时,根本不会调用您的模糊/焦点事件,并且页面可见性API不会在alt + tab上触发。
我以@AndyE的解决方案为基础,并在这里实现了这个(几乎是很好的)解决方案:https : //dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html(对不起,我在JSFiddle上遇到了一些麻烦)。
这也可以在Github上获得:https : //github.com/qmagico/estante-components
这适用于铬/铬。除了不加载iframe内容(知道为什么吗?)之外,它在firefox上有效
无论如何,要解决最后一个问题(4),唯一的方法是在iframe上监听模糊/聚焦事件。如果您对iframe有所控制,则可以使用postMessage API来执行此操作。
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);
});
您可以使用:
(function () {
var requiredResolution = 10; // ms
var checkInterval = 1000; // ms
var tolerance = 20; // percent
var counter = 0;
var expected = checkInterval / requiredResolution;
//console.log('expected:', expected);
window.setInterval(function () {
counter++;
}, requiredResolution);
window.setInterval(function () {
var deviation = 100 * Math.abs(1 - counter / expected);
// console.log('is:', counter, '(off by', deviation , '%)');
if (deviation > tolerance) {
console.warn('Timer resolution not sufficient!');
}
counter = 0;
}, checkInterval);
})();
在HTML 5中,您还可以使用:
onpageshow
:窗口可见时要运行的脚本onpagehide
:隐藏窗口时运行的脚本看到:
这是对Andy E的回答的改编。
这将执行一项任务,例如每30秒刷新一次页面,但前提是该页面可见且集中。
如果无法检测到可见性,则仅使用焦点。
如果用户将页面聚焦,那么它将立即更新
直到任何ajax调用后30秒,页面才会再次更新
var windowFocused = true;
var timeOut2 = null;
$(function(){
$.ajaxSetup ({
cache: false
});
$("#content").ajaxComplete(function(event,request, settings){
set_refresh_page(); // ajax call has just been made, so page doesn't need updating again for 30 seconds
});
// check visibility and focus of window, so as not to keep updating unnecessarily
(function() {
var hidden, change, vis = {
hidden: "visibilitychange",
mozHidden: "mozvisibilitychange",
webkitHidden: "webkitvisibilitychange",
msHidden: "msvisibilitychange",
oHidden: "ovisibilitychange" /* not currently supported */
};
for (hidden in vis) {
if (vis.hasOwnProperty(hidden) && hidden in document) {
change = vis[hidden];
break;
}
}
document.body.className="visible";
if (change){ // this will check the tab visibility instead of window focus
document.addEventListener(change, onchange,false);
}
if(navigator.appName == "Microsoft Internet Explorer")
window.onfocus = document.onfocusin = document.onfocusout = onchangeFocus
else
window.onfocus = window.onblur = onchangeFocus;
function onchangeFocus(evt){
evt = evt || window.event;
if (evt.type == "focus" || evt.type == "focusin"){
windowFocused=true;
}
else if (evt.type == "blur" || evt.type == "focusout"){
windowFocused=false;
}
if (evt.type == "focus"){
update_page(); // only update using window.onfocus, because document.onfocusin can trigger on every click
}
}
function onchange () {
document.body.className = this[hidden] ? "hidden" : "visible";
update_page();
}
function update_page(){
if(windowFocused&&(document.body.className=="visible")){
set_refresh_page(1000);
}
}
})();
set_refresh_page();
})
function get_date_time_string(){
var d = new Date();
var dT = [];
dT.push(d.getDate());
dT.push(d.getMonth())
dT.push(d.getFullYear());
dT.push(d.getHours());
dT.push(d.getMinutes());
dT.push(d.getSeconds());
dT.push(d.getMilliseconds());
return dT.join('_');
}
function do_refresh_page(){
// do tasks here
// e.g. some ajax call to update part of the page.
// (date time parameter will probably force the server not to cache)
// $.ajax({
// type: "POST",
// url: "someUrl.php",
// data: "t=" + get_date_time_string()+"&task=update",
// success: function(html){
// $('#content').html(html);
// }
// });
}
function set_refresh_page(interval){
interval = typeof interval !== 'undefined' ? interval : 30000; // default time = 30 seconds
if(timeOut2 != null) clearTimeout(timeOut2);
timeOut2 = setTimeout(function(){
if((document.body.className=="visible")&&windowFocused){
do_refresh_page();
}
set_refresh_page();
}, interval);
}
对于没有jQuery的解决方案,请查看Visibility.js,其中提供了有关三个页面状态的信息
visible ... page is visible
hidden ... page is not visible
prerender ... page is being prerendered by the browser
以及setInterval的便捷包装
/* Perform action every second if visible */
Visibility.every(1000, function () {
action();
});
/* Perform action every second if visible, every 60 sec if not visible */
Visibility.every(1000, 60*1000, function () {
action();
});
还提供旧版浏览器(IE <10; iOS <7)的后备版本
稍微复杂一点的方法是用来setInterval()
检查鼠标位置并与上一次检查进行比较。如果鼠标没有在设定的时间内移动,则用户可能处于空闲状态。
这具有告诉用户是否空闲的附加优点,而不仅仅是检查窗口是否处于活动状态。
正如许多人指出的那样,这并不总是一种检查用户或浏览器窗口是否空闲的好方法,因为用户甚至可能没有使用鼠标或正在观看视频等。我只是建议一种检查空闲状态的可能方法。
对于angular.js,这是一条指令(基于接受的答案),该指令将使您的控制器对可见性的变化做出反应:
myApp.directive('reactOnWindowFocus', function($parse) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var hidden = "hidden";
var currentlyVisible = true;
var functionOrExpression = $parse(attrs.reactOnWindowFocus);
// 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);
else if ("onfocusin" in document) {
// IE 9 and lower:
document.onfocusin = onshow;
document.onfocusout = onhide;
} else {
// All others:
window.onpageshow = window.onfocus = onshow;
window.onpagehide = window.onblur = onhide;
}
function onchange (evt) {
//occurs both on leaving and on returning
currentlyVisible = !currentlyVisible;
doSomethingIfAppropriate();
}
function onshow(evt) {
//for older browsers
currentlyVisible = true;
doSomethingIfAppropriate();
}
function onhide(evt) {
//for older browsers
currentlyVisible = false;
doSomethingIfAppropriate();
}
function doSomethingIfAppropriate() {
if (currentlyVisible) {
//trigger angular digest cycle in this scope
scope.$apply(function() {
functionOrExpression(scope);
});
}
}
}
};
});
您可以像以下示例一样使用它:<div react-on-window-focus="refresh()">
,refresh()
无论哪个Controller在范围内,范围函数在哪里。
这是一个可靠的现代解决方案。(甜蜜Sh)
document.addEventListener("visibilitychange", () => {
console.log( document.hasFocus() )
})
这将设置一个侦听器,以在激发可见性事件(可能是焦点或模糊)时触发。
如果您想对整个浏览器进行模糊处理:如我所评论,如果浏览器失去焦点,则不会触发任何建议的事件。我的想法是循环计数并在发生事件时重置计数器。如果计数器达到极限,我可以在location.href到另一页。如果您使用开发工具,这也会触发。
var iput=document.getElementById("hiddenInput");
,count=1
;
function check(){
count++;
if(count%2===0){
iput.focus();
}
else{
iput.blur();
}
iput.value=count;
if(count>3){
location.href="http://Nirwana.com";
}
setTimeout(function(){check()},1000);
}
iput.onblur=function(){count=1}
iput.onfocus=function(){count=1}
check();
这是在FF上成功测试的草案。
requestAnimationFrame
API或使用现代功能,即当窗口不可见时(例如,在Chrome中为1秒),降低setTimeout
/ 的频率setInterval
。