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

36,908

Solution 1

Here's a roundup post I wrote for this issue and a workaround in pure JavaScript to solve the encountered problems.

Edited to include a copy of the sourced blog 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.

Solution 2

A working solution is proposed described here: https://stackoverflow.com/a/9502074/698168. It uses a combination of the W3C Page Visibility API, blur/focus and mouse movements. Hidden HTML pages related to Alt+Tab are identified in a probabilistic way (i.e. you cannot determine if your page is hidden with 100% accuracy).

Solution 3

we can do like below when switching between tabs and switching between applications

 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);
Share:
36,908
agbb
Author by

agbb

Updated on July 09, 2022

Comments

  • agbb
    agbb almost 2 years

    The problem is with the behaviour of the event "visibilitychange".

    It's triggered: - When I switch to a different tab inside the browser window.

    • When I click in minimize / restore buttons for the browser window.

    (this is ok)

    It's not triggered: - When I switch to a different window/program using ALT+TAB.

    • When I switch to a different window/program clicking on taskbar.

    (this SHOULD trigger, because, just like when minimizing, the window's visibility may change)


    W3 Page Visibility API Documentation: http://www.w3.org/TR/page-visibility/

    There is no definition of "page visibility" regarding ALT+TAB/program switching in the spec sheet. I'm guessing it has something to do in between the OS and the Browser.


    TESTED IN

    • Browsers: Chrome 40.0.2214.115 m / Firefox 36.0.1 / Internet Explorer 11.0.9600.17107
    • OS: Windows 8.1

    Is there a workaround to fix this behaviour? The implementation is fairly simple, I listen to the "visibilitychange" event using jQuery, and then in its callback, I check for the value of "document.visibilityState", but the problem is that the event is not firing when expected.

    $(document).on('visibilitychange', function() {
    
        if(document.visibilityState == 'hidden') {
            // page is hidden
        } else {
            // page is visible
        }
    });
    

    This can be done without jQuery too, but the ALT+TAB and taskbar switch hide/show expected behaviour is still missing:

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

    I've also tried the ifvisible.js module (https://github.com/serkanyersen/ifvisible.js) but the behaviour is the same.

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

    I haven't tested in other browsers because if I can't make it work in Chrome on Windows I really don't care about the other browsers yet.

    Any help or suggestions?


    UPDATE

    I tried using different vendor prefixes for the event name (visibilitychange, webkitvisibilitychange, mozvisibilitychange, msvisibilitychange) but but still the event is not triggered when I switch to a different program in the taskbar or ALT+TAB, or even if I open the start menu thing in windows with the windows key, which covers the whole screen.

    I can reproduce the exact same issue in Chrome, Firefox and Internet Explorer.

    UPDATE #2

    Here's a roundup post I wrote for this issue and a workaround in pure Javascript to solve the encountered problems.

    UPDATE #3

    Edited to include a copy of the sourced blog post. (see accepted answer)