使用 ALT+TAB 或单击任务栏切换 program/window 时不会触发 visibilitychange 事件

visibilitychange event is not triggered when switching program/window with ALT+TAB or clicking in taskbar

问题出在事件的行为上 "visibilitychange"。

已触发: - 当我切换到浏览器中的另一个选项卡时 window。

(这个可以)

未触发: - 当我使用 ALT+TAB 切换到另一个 window/program 时。

(这应该触发,因为,就像最小化时一样,window 的可见性可能会改变)


W3 页面可见性API 文档http://www.w3.org/TR/page-visibility/

规范中没有关于ALT+TAB/程序切换的"page visibility"定义sheet。我猜它在 OS 和浏览器之间有一些事情要做。


测试在


是否有解决此问题的解决方法?实现相当简单,我使用 jQuery 监听 "visibilitychange" 事件,然后在其回调中检查 "document.visibilityState" 的值,但问题是该事件不是按预期触发。

$(document).on('visibilitychange', function() {

    if(document.visibilityState == 'hidden') {
        // page is hidden
    } else {
        // page is visible
    }
});

这也可以在没有 jQuery 的情况下完成,但是 ALT+TAB 和任务栏开关 hide/show仍然缺少预期的行为:

if(document.addEventListener){
    document.addEventListener("visibilitychange", function() {
        // check for page visibility
    });
}

我也试过 ifvisible.js 模块 (https://github.com/serkanyersen/ifvisible.js) 但行为是一样的。

ifvisible.on('blur', function() {
    // page is hidden
});

ifvisible.on('focus', function() {
    // page is visible
});

我还没有在其他浏览器上测试过,因为如果我不能在 Chrome 和 Windows 上工作,我真的不关心其他浏览器。

有什么帮助或建议吗?


更新

我尝试为事件名称使用不同的供应商前缀(visibilitychange、webkitvisibilitychange、mozvisibilitychange、msvisibilitychange),但是当我切换到任务栏中的其他程序或 ALT+TAB,或者即使我用 windows 键打开 windows 中的开始菜单,它会覆盖整个屏幕。

我可以在 Chrome、Firefox 和 Internet Explorer 中重现完全相同的问题。

更新#2

Here's 我为这个问题写了一篇综述 post 和纯 Javascript 的解决方法来解决遇到的问题。

更新 #3

编辑以包含来源博客的副本 post。 (查看已接受的答案)

Here's 综述 post 我为这个问题写了一个解决方法 JavaScript 来解决遇到的问题。

已编辑以包含来源博客的副本 post:


In any kind of javascript application we develop there may be a feature or any change in the application which reacts according to the current user visibility state, this could be to pause a playing video when the user ALT+TABs to a different window, tracking stats about how the users interact with our application, how often does him switch to a different tab, how long does it take him to return and a lot of performance improvements that can benefit from this kind of API.

The Page Visibility API provides us with two top-level attributes: document.hidden (boolean) and document.visibilityState (which could be any of these strings: “hidden”, “visible”, “prerender”, “unloaded”). This would not be not good enough without an event we could listen to though, that’s why the API also provides the useful visibilitychange event.

So, here’s a basic example on how we could take action on a visibility change:

function handleVisibilityChange() {
  if(document.hidden) {
    // the page is hidden
  } else {
    // the page is visible
  }
}

document.addEventListener("visibilitychange", handleVisibilityChange, false);

We could also check for document.visibilityState value.

Dealing with vendor issues George Berkeley by John Smibert

Some of the implementations on some browsers still need that the attributes or even the event name is vendor-prefixed, this means we may need to listen to the msvisibilitychange event or check for the document.webkitHidden or the document.mozHidden attributes. In order to do so, we should check if any vendor-prefixed attribute is set, and once we know which one is the one used in the current browser (only if there’s the need for a prefix), we can name the event and attributes properly.

Here’s an example approach on how to handle these prefixes:

var browserPrefixes = ['moz', 'ms', 'o', 'webkit'];

// get the correct attribute name
function getHiddenPropertyName(prefix) {
  return (prefix ? prefix + 'Hidden' : 'hidden');
}

// get the correct event name
function getVisibilityEvent(prefix) {
  return (prefix ? prefix : '') + 'visibilitychange';
}

// get current browser vendor prefix
function getBrowserPrefix() {
  for (var i = 0; i < browserPrefixes.length; i++) {
    if(getHiddenPropertyName(browserPrefixes[i]) in document) {
      // return vendor prefix
      return browserPrefixes[i];
    }
  }

  // no vendor prefix needed
  return null;
}

// bind and handle events
var browserPrefix = getBrowserPrefix();

function handleVisibilityChange() {
  if(document[getHiddenPropertyName(browserPrefix )]) {
    // the page is hidden
    console.log('hidden');
  } else {
    // the page is visible
    console.log('visible');
  }
}

document.addEventListener(getVisibilityEvent(browserPrefix), handleVisibilityChange, false);

Other issues There is a challenging issue around the “Page Visibility” definition: how to determine if the application is visible or not if the window focus is lost for another window, but not the actual visibility on the screen? what about different kinds of visibility lost, like ALT+TAB, WIN/MAC key (start menu / dash), taskbar/dock actions, WIN+L (lock screen), window minimize, window close, tab switching. What about the behaviour on mobile devices?

There’s lots of ways in which we may lose or gain visibility and a lot of possible interactions between the browser and the OS, therefore I don’t think there’s a proper and complete “visible page” definition in the W3C spec. This is the definition we get for the document.hidden attribute:

HIDDEN ATTRIBUTE On getting, the hidden attribute MUST return true if the Document contained by the top level browsing context (root window in the browser’s viewport) [HTML5] is not visible at all. The attribute MUST return false if the Document contained by the top level browsing context is at least partially visible on at least one screen.

If the defaultView of the Document is null, on getting, the hidden attribute MUST return true.

To accommodate accessibility tools that are typically full screen but still show a view of the page, when applicable, this attribute MAY return false when the User Agent is not minimized but is fully obscured by other applications.

I’ve found several inconsistencies on when the event is actually fired, for example (Chrome 41.0.2272.101 m, on Windows 8.1) the event is not fired when I ALT+TAB to a different window/program nor when I ALT+TAB again to return, but it IS fired if I CTRL+TAB and then CTRL+SHIFT+TAB to switch between browser tabs. It’s also fired when I click on the minimize button, but it’s not fired if the window is not maximized and I click my editor window which is behing the browser window. So the behaviour of this API and it’s different implementations are still obscure.

A workaround for this, is to compensate taking advantage of the better implemented focus and blur events, and making a custom approach to the whole “Page Visibility” issue using an internal flag to prevent multiple executions, this is what I’ve come up with:

var browserPrefixes = ['moz', 'ms', 'o', 'webkit'],
    isVisible = true; // internal flag, defaults to true

// get the correct attribute name
function getHiddenPropertyName(prefix) {
  return (prefix ? prefix + 'Hidden' : 'hidden');
}

// get the correct event name
function getVisibilityEvent(prefix) {
  return (prefix ? prefix : '') + 'visibilitychange';
}

// get current browser vendor prefix
function getBrowserPrefix() {
  for (var i = 0; i < browserPrefixes.length; i++) {
    if(getHiddenPropertyName(browserPrefixes[i]) in document) {
      // return vendor prefix
      return browserPrefixes[i];
    }
  }

  // no vendor prefix needed
  return null;
}

// bind and handle events
var browserPrefix = getBrowserPrefix(),
    hiddenPropertyName = getHiddenPropertyName(browserPrefix),
    visibilityEventName = getVisibilityEvent(browserPrefix);

function onVisible() {
  // prevent double execution
  if(isVisible) {
    return;
  }
 
  // change flag value
  isVisible = true;
  console.log('visible}

function onHidden() {
  // prevent double execution
  if(!isVisible) {
    return;
  }

  // change flag value
  isVisible = false;
  console.log('hidden}

function handleVisibilityChange(forcedFlag) {
  // forcedFlag is a boolean when this event handler is triggered by a
  // focus or blur eventotherwise it's an Event object
  if(typeof forcedFlag === "boolean") {
    if(forcedFlag) {
      return onVisible();
    }

    return onHidden();
  }

  if(document[hiddenPropertyName]) {
    return onHidden();
  }

  return onVisible();
}

document.addEventListener(visibilityEventName, handleVisibilityChange, false);

// extra event listeners for better behaviour
document.addEventListener('focus', function() {
  handleVisibilityChange(true);
}, false);

document.addEventListener('blur', function() {
  handleVisibilityChange(false);
}, false);

window.addEventListener('focus', function() {
    handleVisibilityChange(true);
}, false);

window.addEventListener('blur', function() {
  handleVisibilityChange(false);
}, false);

I welcome any feedback on this workaround. Some other great sources for ideas on this subject:

Using the Page Visibility API Using PC Hardware more efficiently in HTML5: New Web Performance APIs, Part 2 Introduction to the Page Visibility API Conclusion The technologies of the web are continuously evolving, we’re still recovering from a dark past where tables where the markup king, where semantics didn’t mattered, and they weren’t any standards around how a browser should render a page.

It’s important we push these new standards forward, but sometimes our development requirements make us still need to adapt to these kind of transitions, by handling vendor prefixes, testing in different browsers and differents OSs or depend on third-party tools to properly identify this differences.

We can only hope for a future where the W3C specifications are strictly revised, strictly implemented by the browser developer teams, and maybe one day we will have a common standard for all of us to work with.

As for the Page Visibility API let’s just kinda cite George Berkeley and say that:

“being visible” is being perceived.

此处描述了一个可行的解决方案:。它结合使用了 W3C 页面可见性 API、blur/focus 和鼠标移动。与 Alt+Tab 相关的隐藏 HTML 页面以概率方式识别(即您无法确定您的页面是否以 100% 的准确度隐藏)。

我遇到了一个非常简单的解决方案。

您只需要在将事件侦听器附加到文档时将 false 传递给 useCapture。很有魅力!

 document.addEventListener('visibilitychange', function () {
  // code goes here
}, false)

我们可以在标签页之间切换和应用程序之间切换时像下面这样

 var pageVisible = true;  
 function handleVisibilityChange() {
      if (document.hidden) {
        pageVisible = false;
      } else  {
        pageVisible = true;
      }
      console.log("handleVisibilityChange")
      console.log("pageVisible", pageVisible)
      // some function call
    }
    document.addEventListener("visibilitychange", handleVisibilityChange, false);
    window.addEventListener('focus', function() {
        pageVisible = true;
        // some function call 
    }, false);
    window.addEventListener('blur', function() {
      pageVisible = false;
      // some function call  
    }, false);