有没有办法来检测浏览器窗口是否目前不活动?
我有定期做活动的JavaScript。 当用户没有看到该网站(即窗口或选项卡没有焦点),这将是很好的不运行。
有没有办法做到这一点使用JavaScript?
我的参考点:如果您使用的窗口未激活,则Gmail聊天会播放声音。
自从最初写这个答案以来,由于W3C,一个新的规范已经达到了推荐的地位。 页面可见性API (在MDN上 )现在允许我们更准确地检测页面何时隐藏给用户。
当前浏览器支持:
- Chrome 13+
- Internet Explorer 10+
- Firefox 10+
- Opera 12.10以上[ 阅读笔记 ]
以下代码使用API,在不兼容的浏览器中回退到较不可靠的模糊/聚焦方法。
(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和更低 onfocusout
是必需的 ,而所有其他的则使用onfocus
和onblur
,除了使用onpageshow
和onpagehide
iOS。
我会使用jQuery,因为那么你所要做的就是这样:
$(window).blur(function(){ //your code here }); $(window).focus(function(){ //your code });
或者至less它为我工作。
有三种典型的方法来确定用户是否可以看到HTML页面,但是没有一个能够完美地工作:
-
W3C页面可见性API应该是这样做的(从以下版本开始支持:Firefox 10,MSIE 10,Chrome 13)。 但是,此API仅在浏览器选项卡完全覆盖时引发事件(例如,当用户从一个选项卡更改为另一个选项卡时)。 当无法以100%的准确度确定可见性时,API不会引发事件(例如,Alt + Tab切换到另一个应用程序)。
-
使用基于焦点/模糊的方法会带来很多误报。 例如,如果用户在浏览器窗口顶部显示一个较小的窗口,那么浏览器窗口将失去焦点(
onblur
引发),但用户仍然可以看到它(所以仍然需要刷新)。 另见http://javascript.info/tutorial/focus - 依靠用户活动 (鼠标移动,点击,键入)也会给你很多误报。 想想上面的情况,或者观看video的用户。
为了改善上述不完美的行为,我使用了W3C可见性API,然后使用焦点/模糊和用户活动方法这三种方法的组合来降低误报率。 这允许pipe理以下事件:
- 将浏览器选项卡更改为另一个(100%的准确性,这要归功于W3C页面可见性API)
- 页面可能被另一个窗口隐藏,例如由于Alt + Tab(概率=不是100%准确)
- 用户注意力可能不集中在HTML页面(概率=不是100%准确)
这是如何工作的:当文档失去焦点时,监视文档上的用户活动(例如鼠标移动)以确定窗口是否可见。 页面可见性概率与页面上最后一次用户活动的时间成反比:如果用户长时间不在文档上进行任何活动,页面可能不可见。 下面的代码模仿了W3C页面可见性API:它的行为方式相同,但误报率很小。 它具有多浏览器的优点(在Firefox 5,Firefox 10,MSIE 9,MSIE 7,Safari 5,Chrome 9上testing)。
<div id =“x”> </ div> <SCRIPT> / ** 将处理程序注册到给定对象的事件。 @param obj将会引发事件的对象 @param evType事件types:点击,按键,鼠标hover,... 事件处理函数@参数 @param isCapturing设置事件模式(true =捕获事件,false =冒泡事件) @return如果事件处理程序已正确连接,则返回true * / function addEvent(obj,evType,fn,isCapturing){ if(isCapturing == null)isCapturing = false; if(obj.addEventListener){ // Firefox obj.addEventListener(evType,fn,isCapturing); 返回true; } else if(obj.attachEvent){ // MSIE var r = obj.attachEvent('on'+ evType,fn); 返回r; } else { 返回false; } } //注册到潜在的页面可见性变化 addEvent(document,“potentialvisilitychange”,function(event){ document.potEtiallyHiddenSince =“+ document.potentiallyHiddenSince +”s“; document.getElementById(”x“)。innerHTML + =”potentialVisilityChange:potentialHidden =“+ document.potentialHidden +”,document.potentiallyHiddenSince =“+ }); //注册到W3C页面可见性API var hidden = null; var visibilityChange = null; if(typeof document.mozHidden!==“undefined”){ 隐藏= “mozHidden”; visibilityChange = “mozvisibilitychange”; } else if(typeof document.msHidden!==“undefined”){ 隐藏= “msHidden”; visibilityChange = “msvisibilitychange”; } else if(typeof document.webkitHidden!==“undefined”){ 隐藏= “webkitHidden”; visibilityChange = “webkitvisibilitychange”; 其他如果(typeof document.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,//以秒为单位 init:function(){ 函数setAsNotHidden(){ var dispatchEventRequired = document.potentialHidden; document.potentialHidden = FALSE; document.potentiallyHiddenSince = 0; if(dispatchEventRequired)dispatchPageVisibilityChangeEvent(); } 函数initPotentiallyHiddenDetection(){ 如果(!hasFocusLocal){ //窗口没有焦点=>检查窗口中的用户活动 lastActionDate = new Date(); if(timeoutHandler!= null){ clearTimeout(timeoutHandler); } timeoutHandler = setTimeout(checkPageVisibility,potentialPageVisibility.pageVisibilityChangeThreshold * 1000 + 100); // + 100毫秒,以避免在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; if(potentialHiddenDuration> = potentialPageVisibility.pageVisibilityChangeThreshold &&!document.potentialHidden){ //页面可见性变化阈值raiched =>提高均匀度 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:点击”。 }); addEvent(document,“pagehide”,function(event){ 的document.getElementById( “X”)的innerHTML + = “pagehide / DOC:点击”。 }); addEvent(窗口,“pageshow”,函数(事件){ 。的document.getElementById( “X”)的innerHTML + = “pageshow /赢:点击”; //在页面首次显示时引发 }); addEvent(窗口,“pagehide”,函数(事件){ 。的document.getElementById( “X”)的innerHTML + = “pagehide /赢:点击”; //没有提出 }); addEvent(document,“mousemove”,function(event){ lastActionDate = new Date(); }); addEvent(文档,“mouseover”,函数(事件){ hasMouseOver = TRUE; setAsNotHidden(); }); addEvent(document,“mouseout”,function(event){ hasMouseOver = FALSE; initPotentiallyHiddenDetection(); }); addEvent(窗口,“模糊”,function(事件){ hasFocusLocal = FALSE; initPotentiallyHiddenDetection(); }); addEvent(窗口,“焦点”,function(事件){ hasFocusLocal = TRUE; setAsNotHidden(); }); setAsNotHidden(); } } potentialPageVisibility.pageVisibilityChangeThreshold = 4; //testing4秒 potentialPageVisibility.init(); </ SCRIPT>
由于目前没有正确的跨浏览器解决scheme,您最好在您的网站上重新考虑禁用定期活动。
在GitHub上有一个整洁的库:
https://github.com/serkanyersen/ifvisible.js
例:
// If page is visible right now if( ifvisible.now() ){ // Display pop-up openPopUp(); }
我已经在所有浏览器上testing过版本1.0.1,并且可以确认它适用于:
- IE9,IE10
- FF 26.0
- Chrome 34.0
…也许所有更新的版本。
不完全适用于:
- IE8 – 总是指示标签页/窗口当前处于活动状态(
.now()
对我来说总是返回true
)
这真的很棘手。 鉴于以下要求,似乎没有解决scheme。
- 该页面包含您无法控制的iframe
- 无论TAB更改(ctrl + tab)还是窗口更改(alt + tab)触发的更改都要跟踪可见性状态更改
这是因为:
- 页面可见性API可以可靠地告诉你一个选项卡的变化(即使是在使用iframe的情况下),但是当用户改变窗口时它不能告诉你。
- 只要iframe没有焦点,听窗口模糊/焦点事件就可以检测到alt +选项卡和ctrl +选项卡。
鉴于这些限制,可以实现一个解决scheme,它结合了 – 页面可见性API – 窗口模糊/焦点 – document.activeElement
这是能够:
- 1)当父页面有焦点时,按Ctrl +标签:是
- 2)当iframe具有焦点时,按Ctrl + Tab:YES
- 3)当父页面有焦点时,alt +标签:是
- 4)当iframe有焦点时alt + tab: NO < – 无赖
当iframe具有焦点时,您的模糊/焦点事件根本不会被调用,并且页面可见性API将不会在alt +选项卡上触发。
我build立在@ AndyE的解决scheme,并在这里实现这个(几乎是好的)解决scheme: https ://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html(对不起,我有一些JSFiddle的麻烦)。
这也可以在Github上find: https : //github.com/qmagico/estante-components
这适用于铬/铬。 它适用于Firefox,除了它不加载iframe的内容(任何想法为什么?)
无论如何,要解决最后一个问题(4),唯一的方法就是在iframe上侦听模糊/焦点事件。 如果你对iframe有一些控制,你可以使用postMessage API来做到这一点。
https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test2.html
我还没有testing过足够多的浏览器。 如果你可以find更多的信息,这不起作用,请让我知道下面的评论。
我为我的应用程序创build一个彗星聊天,当我收到来自另一个用户的消息时,我使用:
if(new_message){ if(!document.hasFocus()){ audio.play(); document.title="Have new messages"; } else{ audio.stop(); document.title="Application Name"; } }
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); });
我开始使用社区维基答案,但意识到它没有在Chrome中检测到ALT键。 这是因为它使用了第一个可用的事件源,在这种情况下,它是页面可见性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) { 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'); });
使用: 页面可见性API
document.addEventListener( 'visibilitychange' , function() { if (document.hidden) { console.log('bye'); } else { console.log('well back'); } }, false );
你可以使用:
(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); })();
稍微复杂一点的方法是使用setInterval()
来检查鼠标的位置,并与上次检查进行比较。 如果鼠标没有在一定的时间内移动,用户可能是空闲的。
这具有额外的优点,即告诉用户是否空闲,而不是仅仅检查窗口是否处于活动状态。
正如很多人所指出的,这并不总是检查用户或浏览器窗口是否空闲的好方法,因为用户甚至可能不使用鼠标或正在观看video等。 我只是build议一种可能的方法来检查空闲状态。
这是对安迪的回答。
这将执行一项任务,例如每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 // eg 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); }
在HTML 5中,您也可以使用:
-
onpageshow
:当窗口变得可见时要运行的脚本 -
onpagehide
:隐藏窗口时要运行的脚本
看到:
对于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的作用域函数。
对于没有jQuery的解决scheme,请查看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)也可以使用
如果你想对整个浏览器模糊处理 :正如我评论的,如果浏览器松散焦点没有build议的事件触发。 我的想法是计数在一个循环,并重置计数器,如果事件发生。 如果计数器达到一个限制,我做一个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();
这是一个FFtesting成功的草稿。
只是想补充一点:这个问题不清楚写的。 “当用户没有看到该网站(即,窗口或标签没有焦点)…”
当没有焦点时,我可以看一个网站。 大多数桌面系统能够并行显示窗口:)
这就是为什么页面可见性API可能是正确的答案,因为当“用户无法看到更新”时,它会阻止更新站点,这与“选项卡没有焦点”非常不同。