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

问题描述 投票:526回答:18

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

有没有办法使用JavaScript?

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

javascript browser focus window
18个回答
629
投票

自从最初撰写此答案以来,由于W3C,新规范已达到推荐状态。 Page Visibility API(在MDN上)现在允许我们更准确地检测何时页面对用户隐藏。

目前的浏览器支持

  • Chrome 13+
  • Internet Explorer 10+
  • Firefox 10+
  • Opera 12.10+ [read notes]

以下代码使用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,除了使用onpageshowonpagehide的iOS。


3
投票

你可以用:

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

})();

3
投票

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

  • onpageshow:当窗口变得可见时运行的脚本
  • onpagehide:隐藏窗口时要运行的脚本

看到:


2
投票

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

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

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


2
投票

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

1
投票

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


1
投票

这适用于chrome 67,firefox 67,

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

0
投票

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


0
投票

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

当它没有焦点时我可以看一个网站。大多数桌面系统都能够并行显示窗口:)

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


124
投票

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

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

或者至少它对我有用。


44
投票

有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,然后是焦点/模糊和用户活动方法,以降低误报率。这允许管理以下事件:

  • 将浏览器选项卡更改为另一个(由于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>

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


23
投票

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

13
投票

使用:Page Visibility API

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

我可以用吗 ? http://caniuse.com/#feat=pagevisibility


12
投票

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

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

9
投票

我开始使用社区维基回答,但意识到它没有检测到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');
});

7
投票

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

  • 该页面包含您无法控制的iframe
  • 无论TAB更改(ctrl + tab)或窗口更改(alt + tab)触发更改,您都希望跟踪可见性状态更改

这是因为:

  • Visibility API页面可以可靠地告诉您标签更改(即使使用iframe),但它无法告诉您用户何时更改窗口。
  • 只要iframe没有焦点,聆听窗口模糊/焦点事件就可以检测到alt +标签和ctrl +标签。

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

这能够:

  • 1)父页面具有焦点时的ctrl + tab:YES
  • 2)当iframe有焦点时按ctrl + tab:YES
  • 3)父页面具有焦点时的alt + tab:YES
  • 4)当iframe有焦点时alt + tab:NO < - 无赖

当iframe具有焦点时,您的模糊/焦点事件根本不会被调用,并且页面Visibility API将不会在alt + tab上触发。

我建立了@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

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


4
投票
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/

© www.soinside.com 2019 - 2024. All rights reserved.