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

IT技术 javascript browser focus window
2021-01-10 19:34:02

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

有没有办法使用 JavaScript 做到这一点?

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

6个回答

自从最初编写此答案以来,由于 W3C ,新规范已达到推荐状态。网页浏览权限API(在MDN)现在允许当一个页面被隐藏到用户我们更准确地检测。

document.addEventListener("visibilitychange", onchange);

当前浏览器支持:

  • 铬 13+
  • Internet Explorer 10+
  • 火狐 10+
  • Opera 12.10+ [阅读笔记]

以下代码在不兼容的浏览器中退回到不太可靠的模糊/聚焦方法:

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

onfocusinandonfocusoutIE 9 及更低版本必需的,而所有其他版本都使用onfocusonblur,iOS 除外,它使用onpageshowonpagehide

@bellpeace:IE应当传播focusinfocusout从的IFRAME到上部窗口。对于较新的浏览器,您只需要处理每个 iframe对象focusblur事件window您应该使用我刚刚添加的更新代码,它至少可以在较新的浏览器中涵盖这些情况。
2021-03-14 19:34:02
@JulienKronegg:这就是为什么我的回答特别提到页面可见性 API 在我最初写下我的答案后进入工作草案状态。焦点/模糊方法为旧浏览器提供有限的功能。绑定到其他事件,如您的回答,并没有涵盖更多,并且更有可能出现行为差异(例如,当光标下方弹出窗口时,IE 不会触发鼠标移出)。我建议更合适的操作是显示一条消息或图标,向用户表明由于页面不活动而更新频率可能较低。
2021-03-24 19:34:02
@Heliodor:我想暂时将代码保留在答案中。它从来没有打算成为一个剪切和粘贴的完整解决方案,因为实现者可能希望避免在主体上设置类并完全采取完全不同的操作(例如停止和启动计时器)。
2021-03-24 19:34:02
@AndyE 我在铬上试过这个解决方案。如果我更改选项卡,它会起作用,但如果我更改窗口(ALT + 选项卡),则不会。应该是?这是一个小提琴 - jsfiddle.net/8a9N6/17
2021-04-03 19:34:02
@AndyE 您的解决方案似乎仅在用户更改选项卡或最小化/最大化窗口时才有效。但是,如果用户使选项卡处于活动状态,则不会触发 onchange 事件,而是从任务栏中最大化另一个程序。这种情况有解决方案吗?谢谢!
2021-04-04 19:34:02

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

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

或者至少它对我有用。

在 Firefox 中,如果您在 firebug 控制台(在同一页面上)内单击,window将失去焦点,这是正确的,但取决于您的意图可能不是您需要的。
2021-03-22 19:34:02
对我来说,在 iframe 中调用了两次
2021-03-23 19:34:02
页面加载时 BLUR 和 FOCUS 都会触发。当我从我的页面打开一个新窗口时,什么也没有发生,但是一旦新窗口关闭,两个事件都会触发:/(使用 IE8)
2021-03-24 19:34:02
此解决方案不适用于 iPad 请使用“pageshow”事件
2021-04-03 19:34:02
这不再适用于当前版本的现代浏览器,请参阅已批准的答案(页面可见性 API)
2021-04-05 19:34:02

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

  • W3C网页浏览权限API应该做到这一点(支持,因为:火狐10,MSIE 10,铬13)。但是,此 API 仅在浏览器选项卡被完全覆盖时(例如,当用户从一个选项卡更改为另一个选项卡时)才会引发事件。当无法以 100% 的准确度确定可见性时(例如 Alt+Tab 切换到另一个应用程序),API 不会引发事件。

  • 使用基于焦点/模糊的方法会给你带来很多误报。例如,如果用户在浏览器窗口的顶部显示一个较小的窗口,则浏览器窗口将失去焦点(onblur升高)但用户仍然可以看到它(因此它仍然需要刷新)。另请参阅http://javascript.info/tutorial/focus

  • 依赖于用户活动(鼠标移动、点击、键入的键)也会给你很多误报。考虑与上述相同的案例,或者用户观看视频。

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

  • 将浏览器选项卡更改为另一个(100% 准确,感谢 W3C 页面可见性 API)
  • 页面可能被另一个窗口隐藏,例如由于 Alt+Tab(概率 = 不是 100% 准确)
  • 用户注意力可能没有集中在 HTML 页面上(概率 = 不是 100% 准确)

它是这样工作的:当文档失去焦点时,监视文档上的用户活动(例如鼠标移动)以确定窗口是否可见。页面可见概率与用户最后一次在页面上活动的时间成反比:如果用户长时间没有对文档进行任何活动,则该页面很可能是不可见的。下面的代码模仿了 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){
      if (isCapturing==null) isCapturing=false; 
      如果(obj.addEventListener){
        // 火狐
        obj.addEventListener(evType, fn, isCapturing);
        返回真;
      } else if (obj.attachEvent){
        // MSIE
        var r = obj.attachEvent('on'+evType, fn);
        返回 r;
      } 别的 {
        返回假;
      }
    }

    // 注册到潜在的页面可见性变化
    addEvent(文档,“潜在可见性变化”,函数(事件){
      document.getElementById("x").innerHTML+="potentialVisilityChange: potentialHidden="+document.potentialHidden+", document.potentiallyHiddenSince="+document.potentiallyHiddenSince+" s<br>";
    });

    // 注册到 W3C 页面可见性 API
    变量隐藏=空;
    varvisibilityChange=null;
    if (typeof document.mozHidden !== "undefined") {
      隐藏=“mozHidden”;
      visibilityChange="mozvisibilitychange";
    } else if (typeof document.msHidden !== "undefined") {
      隐藏=“msHidden”;
      visibilityChange="msvisibilitychange";
    } else if (typeof document.webkitHidden!=="undefined") {
      hidden="webkitHidden";
      visibilityChange="webkitvisibilitychange";
    } else if (typeof document.hidden !=="hidden") {
      隐藏=“隐藏”;
      可见性变化=“可见性变化”;
    }
    如果(隐藏!=空&&可见性变化!=空){
      添加事件(文档,可见性变化,功能(事件){
        document.getElementById("x").innerHTML+=visibilityChange+": "+hidden+"="+document[hidden]+"<br>";
      });
    }


    var potentialPageVisibility = {
      pageVisibilityChangeThreshold:3*3600, // 以秒为单位
      初始化:函数(){
        函数 setAsNotHidden() {
          var dispatchEventRequired=document.potentialHidden;
          document.potentialHidden=false;
          document.potentiallyHiddenSince=0;
          if (dispatchEventRequired) dispatchPageVisibilityChangeEvent();
        }

        函数 initPotentiallyHiddenDetection() {
          如果(!hasFocusLocal){
            // 窗口没有焦点 => 检查窗口中的用户活动
            lastActionDate=new Date();
            如果(超时处理器!=空){
              clearTimeout(timeoutHandler);
            }
            timeoutHandler = setTimeout(checkPageVisibility, potentialPageVisibility.pageVisibilityChangeThreshold*1000+100); // +100 ms 以避免 Firefox 下的舍入问题
          }
        }

        函数 dispatchPageVisibilityChangeEvent() {
          UnifiedVisilityChangeEventDispatchAllowed=false;
          var evt = document.createEvent("事件");
          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){
            // 页面可见性更改阈值 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.getElementById("x").innerHTML+="pageshow/doc:<br>";
        });
        addEvent(文档,“页面隐藏”,函数(事件){
          document.getElementById("x").innerHTML+="pagehide/doc:<br>";
        });
        添加事件(窗口,“页面展示”,函数(事件){
          document.getElementById("x").innerHTML+="pageshow/win:<br>"; // 当页面第一次显示时引发
        });
        添加事件(窗口,“页面隐藏”,函数(事件){
          document.getElementById("x").innerHTML+="pagehide/win:<br>"; // 没有提升
        });
        addEvent(文档,“鼠标移动”,函数(事件){
          lastActionDate=new Date();
        });
        addEvent(文档,“鼠标悬停”,函数(事件){
          hasMouseOver=true;
          setAsNotHidden();
        });
        addEvent(文档,“鼠标移出”,函数(事件){
          hasMouseOver=false;
          initPotentiallyHiddenDetection();
        });
        addEvent(窗口,“模糊”,函数(事件){
          hasFocusLocal=false;
          initPotentiallyHiddenDetection();
        });
        addEvent(窗口,“焦点”,函数(事件){
          hasFocusLocal=true;
          setAsNotHidden();
        });
        setAsNotHidden();
      }
    }

    potentialPageVisibility.pageVisibilityChangeThreshold=4; // 4 秒进行测试
    potentialPageVisibility.init();
    </脚本>

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

@kiran:实际上它与 Alt+Tab 一起工作。当您按 Alt+Tab 时,您无法确定页面是否隐藏,因为您可能会切换到较小的窗口,因此无法保证您的页面完全隐藏。这就是为什么我使用“潜在隐藏”的概念(在示例中,阈值设置为 4 秒,因此您需要使用 Alt+Tab 切换到另一个窗口至少 4 秒)。但是你的评论表明答案不是那么清楚,所以我改写了它。
2021-03-12 19:34:02
在字符串 'undefined' 上使用严格的比较运算符而不是 undefined 关键字是否会导致上述代码中的误报?
2021-03-21 19:34:02
@Caleb 不,我说的是网页前面的另一个应用程序(例如计算器)。在这种情况下,网页失去焦点,但仍然能够接收一些事件(例如鼠标悬停事件)。
2021-03-22 19:34:02
@Jacob 我很高兴你喜欢我的解决方案。随意将其推广到 GitHub 项目中。我给了带有许可 Creative Commons BY creativecommons.org/licenses/by/4.0的代码
2021-03-25 19:34:02
@JulienKronegg 我认为这是最好的解决方案。但是,上面的代码非常需要一些重构和抽象。为什么不上传到GitHub,让社区重构呢?
2021-03-28 19:34:02

使用: 页面可见性 API

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

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

我认为 OP 不是在谈论 ide 的功能
2021-03-21 19:34:02
问题不在于页面可见性。这是关于不活跃/活跃
2021-03-29 19:34:02
我也不是在谈论ide。我正在谈论到另一个应用程序的 alt-tabbing/cmd-tabbing。突然页面不活动。页面可见性 api 不能帮助我知道页面是否处于活动状态,它只能帮助我知道是否可能不可见。
2021-04-02 19:34:02

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
  • 铬 34.0

...可能所有较新的版本。

不完全适用于:

  • IE8 - 始终指示选项卡/窗口当前处于活动状态(.now()始终true为我返回
接受的答案会导致 IE9 出现问题。这个库很好用。
2021-03-15 19:34:02