有没有办法检测浏览器窗口当前是否处于活动状态?

loading...


506

我有定期进行活动的JavaScript . 当用户没有看到该站点时(即窗口或选项卡没有焦点),不运行会很好 .

有没有办法使用JavaScript?

我的参考点:如果您使用的窗口未处于活动状态,则Gmail聊天会播放声音 .

18回答

  • 609

    自从最初编写此答案以来,由于W3C,新规范已达到推荐状态 . Page Visibility 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"});
    })();
    

    onfocusinonfocusoutrequired for IE 9 and lower,而所有其他人都使用 onfocusonblur ,iOS除外,它使用 onpageshowonpagehide .


  • 7

    我会使用jQuery,因为那样你所要做的就是:

    $(window).blur(function(){
      //your code here
    });
    $(window).focus(function(){
      //your code
    });
    

    或者至少它对我有用 .


  • 1

    有3种典型的方法用于确定用户是否可以看到HTML页面,但是它们都不能完美地工作:

    • W3C Page Visibility API应该这样做(支持自:Firefox 10,MSIE 10,Chrome 13) . 但是,此API仅在完全覆盖浏览器选项卡时(例如,当用户从一个选项卡更改为另一个选项卡时)引发事件 . 如果无法以100%的准确度确定可见性,则API不会引发事件(例如,Alt Tab切换到另一个应用程序) .

    • 使用 focus/blur based methods 会给你带来很多误报 . 例如,如果用户在浏览器窗口顶部显示较小的窗口,则浏览器窗口将失去焦点( onblur 已提升),但用户仍然可以看到它(因此仍需要刷新) . 另见http://javascript.info/tutorial/focus

    • Relying on user activity (鼠标移动,点击,键入键入)也会给你带来很多误报 . 考虑与上述相同的情况,或观看视频的用户 .

    为了改善上述不完美行为,我结合使用了3种方法:W3C Visibility API,然后是焦点/模糊和用户活动方法,以降低误报率 . 这允许管理以下事件:

    • 将浏览器标签更改为另一个(由于W3C页面可见性API,100%准确度)

    • 页面可能被另一个窗口隐藏,例如由于Alt Tab(概率=不是100%准确)

    • 用户注意力可能不会集中在HTML页面上(概率=不是100%准确)

    这是它的工作原理:当文档松开焦点时,监视文档上的用户活动(例如鼠标移动)以确定窗口是否可见 . 页面可见性概率与页面上最后一次用户活动的时间成反比:如果用户长时间未对文档进行任何活动,则该页面很可能不可见 . 下面的代码模仿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>
    

    由于目前没有可用的跨浏览器解决方案而没有误报,因此您最好三思而后行,禁用网站上的定期活动 .


  • 122

    GitHub上有一个整洁的库:

    https://github.com/serkanyersen/ifvisible.js

    例:

    // If page is visible right now
    if( ifvisible.now() ){
      // Display pop-up
      openPopUp();
    }
    

    我已经在我拥有的所有浏览器上测试了1.0.1版,并且可以确认它适用于:

    • IE9,IE10

    • FF 26.0

    • Chrome 34.0

    ......可能还有所有新版本 .

    不完全适用于:

    • IE8 - 始终指示选项卡/窗口当前处于活动状态( .now() 总是为我返回 true

  • 4

    我为我的应用程序创建了Comet Chat,当我收到来自其他用户的消息时,我使用:

    if(new_message){
        if(!document.hasFocus()){
            audio.play();
            document.title="Have new messages";
        }
        else{
            audio.stop();
            document.title="Application Name";
        } 
    }
    

  • 2

    Using : Page Visibility API

    document.addEventListener( 'visibilitychange' , function() {
        if (document.hidden) {
            console.log('bye');
        } else {
            console.log('well back');
        }
    }, false );
    

    Can i use ? http://caniuse.com/#feat=pagevisibility


  • 2

    这真的很棘手 . 鉴于以下要求,似乎没有解决方案 .

    • 该页面包含您无法控制的iframe

    • 无论TAB更改(ctrl选项卡)或窗口更改(alt选项卡)触发更改,您都希望跟踪可见性状态更改

    这是因为:

    • 页面可见性API可以可靠地告诉您标签更改(即使使用iframe),但它无法告诉您用户何时更改窗口 .

    • 只要iframe没有焦点,聆听窗口模糊/焦点事件就可以检测到alt选项卡和ctrl选项卡 .

    鉴于这些限制,可以实现一个结合的解决方案 - 页面可见性API - 窗口模糊/焦点 - document.activeElement

    这能够:

    • 1)父页面具有焦点时的ctrl选项卡:YES

    • 2)iframe具有焦点时的ctrl选项卡:是

    • 3)父页面具有焦点时的alt选项卡:是
      当iframe有焦点时

    • 4)alt选项卡:NO < - bummer

    当iframe有焦点,您的模糊/焦点事件根本不会被调用,并且页面Visibility API不会在alt选项卡上触发 .

    我在@ AndyE的解决方案的基础上构建了这个(几乎不错的)解决方案:https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html(对不起,我在JSFiddle上遇到了一些麻烦) .

    这也可以在Github上找到:https://github.com/qmagico/estante-components

    这适用于铬/铬 . 它类似于firefox,除了它不加载iframe内容(任何想法为什么?)

    无论如何,要解决最后一个问题(4),你唯一能做的就是在iframe上监听模糊/焦点事件 . 如果您对iframe有一定的控制权,则可以使用postMessage API来执行此操作 .

    https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test2.html

    我还没有用足够的浏览器测试过这个 . 如果您可以找到有关这不起作用的更多信息,请在下面的评论中告诉我们 .


  • 3

    我开始使用社区维基回答,但意识到它没有检测到Chrome中的alt-tab事件 . 这是因为它使用了第一个可用的事件源,在这种情况下,它是页面可见性API,在Chrome中似乎不跟踪alt-tabbing .

    我决定稍微修改脚本以跟踪页面焦点更改的所有可能事件 . 这是一个你可以参与的功能:

    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)()});
        } else if ('mozHidden' in document) {
            document.addEventListener('mozvisibilitychange',
                function() {(document.mozHidden ? unfocused : focused)()});
        } else if ('webkitHidden' in document) {
            document.addEventListener('webkitvisibilitychange',
                function() {(document.webkitHidden ? unfocused : focused)()});
        } else if ('msHidden' in document) {
            document.addEventListener('msvisibilitychange',
                function() {(document.msHidden ? unfocused : focused)()});
        } else if ('onfocusin' in document) {
            // IE 9 and lower:
            document.onfocusin = focused;
            document.onfocusout = unfocused;
        } else {
            // All others:
            window.onpageshow = window.onfocus = focused;
            window.onpagehide = window.onblur = unfocused;
        }
    };
    

    像这样用它:

    onVisibilityChange(function(visible) {
        console.log('the page is now', visible ? 'focused' : 'unfocused');
    });
    

  • 0
    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);
    });
    

    http://jsfiddle.net/ARTsinn/JTxQY/


  • 1

    你可以用:

    (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);
    
    })();
    

  • 11

    在HTML 5中,您还可以使用:

    • onpageshow :窗口变为可见时运行的脚本

    • onpagehide :隐藏窗口时要运行的脚本

    看到:


  • 23

    稍微复杂一点的方法是使用 setInterval() 检查鼠标位置并与上次检查进行比较 . 如果鼠标未在设定的时间内移动,则用户可能处于空闲状态 .

    这具有告知用户是否空闲的附加优点,而不仅仅是检查窗口是否处于活动状态 .

    正如许多人所指出的那样,这并不总是检查用户或浏览器窗口是否空闲的好方法,因为用户可能甚至没有使用鼠标或正在观看视频等 . 我只是建议一种可能的方法来检查闲置 .


  • 9

    这是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);
    }
    

  • 42

    对于没有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)的后备也可用


  • 1

    对于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() 是范围内的范围内的范围函数 .


  • 0

    这适用于chrome 67,firefox 67,

    if(!document.hasFocus()) {
        // do stuff
    }
    

  • 3

    如果你想行动 on 整个 browser blur :正如我评论的那样,如果浏览器松散焦点,则没有任何建议事件触发 . 我的想法是在循环中计数并在事件触发时重置计数器 . 如果计数器达到限制,我会将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上成功测试的草案 .


  • 7

    只是想补充一下:问题不清楚 . “当用户没有看到该网站时(即窗口或标签没有焦点)......”

    我可以在没有焦点的情况下查看网站 . 大多数桌面系统都能够并行显示窗口:)

    这就是为什么页面可见性API可能是正确的答案,因为当“用户无法看到更新”时,它会阻止更新网站,这可能与“选项卡没有焦点”非常不同 .

loading...

评论

暂时没有评论!