722

I have JavaScript that is doing activity periodically. When the user is not looking at the site (i.e., the window or tab does not have focus), it'd be nice to not run.

Is there a way to do this using JavaScript?

My reference point: Gmail Chat plays a sound if the window you're using isn't active.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Luke Francl
  • 31,028
  • 18
  • 69
  • 91
  • 12
    For those who are not satisfied with the answers below, check out the [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/DOM/window.requestAnimationFrame) API, or use the modern feature that the frequency of `setTimeout`/`setInterval` is reduced when the window is not visible (1 sec in Chrome, for example). – Rob W Feb 10 '13 at 17:05
  • 4
    document.body.onblur=function(e){console.log('lama');} worked for non focused elements. – WhyMe Jun 05 '13 at 01:03
  • 3
    See [this answer](http://stackoverflow.com/a/9634295/96656) for a cross-browser compatible solution that uses the W3C Page Visibility API, falling back to `blur`/`focus` in browsers that don’t support it. – Mathias Bynens Dec 03 '13 at 11:45
  • 8
    80% of the answers below **are not answers to this question**. The question asks about **not currently active** but tons of answer below are about **not visible** which is not an answer to this question. They should arguably be flagged as "not an answer" – gman Aug 31 '18 at 07:19
  • 1
    Most people talk about *not active* when they mean *not active and not visible*. Simply *not active* is easy - just handle window `blur`/`focus` events... that will be of limited use though, since a window can be inactive but fully or partially visible (there are also "preview" icons in some taskbars that people expect to continue being updated). – rustyx Jun 12 '20 at 11:23
  • 1
    Twitter refreshes a page if at the top when one has been away then returns to that tab or window when there are new messages, maybe someone would like to find their methods. – gseattle Jan 15 '21 at 21:02

25 Answers25

813

Since originally writing this answer, a new specification has reached recommendation status thanks to the W3C. The Page Visibility API (on MDN) now allows us to more accurately detect when a page is hidden to the user.

document.addEventListener("visibilitychange", onchange);

Current browser support:

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

The following code falls back to the less reliable blur/focus method in incompatible browsers:

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

onfocusin and onfocusout are required for IE 9 and lower, while all others make use of onfocus and onblur, except for iOS, which uses onpageshow and onpagehide.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • Perfect... I didn't ever think I needed to do this, until my boss asked me to implement it! – Steve Hill Oct 08 '10 at 14:21
  • Also consider http://stackoverflow.com/questions/831686/how-can-i-detect-whether-a-browser-window-is-focused-or-not to detect it for the first. – Ali Shakiba Jun 29 '11 at 12:27
  • How would you tell the difference between the case when user puts the tab in background and the case where she just switches to other (i)frame? – zpavlinovic May 30 '12 at 20:45
  • 2
    @bellpeace: IE should propagate `focusin` and `focusout` from the iframe to the upper window. For newer browsers, you'd just have to handle the `focus` and `blur` events on each iframe's `window` object. You should use the updated code I just added which will at least cover those cases in newer browsers. – Andy E May 30 '12 at 23:13
  • Relying on focus/blur methods do not work, see http://stackoverflow.com/a/9502074/698168 – Julien Kronegg Sep 07 '12 at 14:31
  • 4
    @JulienKronegg: that's why my answer specifically mentions the Page Visibility API which entered working draft status after I originally wrote my answer. The focus/blur methods provide limited functionality for older browsers. Binding to other events, as in your answer, doesn't cover a great deal more than this and is more at risk of behavioural differences (like IE not firing mouseout when a window pops up beneath the cursor). I would suggest a more appropriate action would be to display a message or icon indicating to the user that updates may be less frequent due to page inactivity. – Andy E Sep 07 '12 at 16:12
  • `/*@cc_on!@*/false` is stupid. Use `document.focusin !== undefined` – Raynos Dec 21 '12 at 01:03
  • 1
    @Raynos: you know where the edit button is... and you can do whatever you like with the function :-P – Andy E Dec 21 '12 at 09:33
  • @AndyE sign up to github and npm and open source all your functions! Do the world a favor. – Raynos Dec 21 '12 at 22:29
  • @Raynos How is `document.focusin !== undefined` a suitable replacement for `/*@cc_on!@*/false`? Besides the potential issues with minification, why is using `@cc_on` "stupid"? – namuol Dec 27 '12 at 23:25
  • Great answer! Now — how do I, as a browser user, disable this ability? Making me keep an ad window active for a set period of time before you let me see the content is not cool. – Atario Feb 22 '13 at 22:30
  • @Atario: I've never seen an implementation like that, but it's rather clever in a somewhat evil way. I don't think there's an easy way to disable the Page Visibility API in browsers. One way to combat this script in Firefox or Chrome would be to write a GreaseMonkey script that sets a `hidden` property on the document to `false`. This would force the script above to set the non-prefixed event handler which wouldn't fire in current versions of those browsers, but obviously it wouldn't work forever. – Andy E Feb 24 '13 at 11:04
  • I think this may be totally unrelated, to the original answer, but I found it very helpful with one minor alteration: for IE9, I needed to use `ondomfocusin` instead of `onfocusin` (and `out`, too). – jeffjenx Apr 16 '13 at 13:39
  • 17
    @AndyE I tried this solution on chromium. It works if I change tabs, but it doesn't if I change windows (ALT+tab). Should it? Here's a fiddle - http://jsfiddle.net/8a9N6/17/ – Tony Lâmpada Sep 16 '13 at 21:25
  • @TonyLâmpada: Actually, the [specification states](http://www.w3.org/TR/page-visibility/#sec-visibilitychange-event) that *"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."*. So I guess it is broken then... [bug filed](https://code.google.com/p/chromium/issues/detail?id=293128). – Andy E Sep 17 '13 at 02:09
  • It seems this problems affects other browsers too (Firefox, at least). I used a combination of the VisibilityAPI + blur/focus to implement another solution (added an answer below) – Tony Lâmpada Sep 17 '13 at 17:44
  • @Tony: for me, Firefox does fire the event on minimise/maximise, but not for other conditions. I [filed an issue](https://bugzilla.mozilla.org/show_bug.cgi?id=917090) on the Mozilla tracker shortly after filing the one for Chromium. Don't forget to vote on the bugs if you're registered ;-) – Andy E Sep 18 '13 at 10:21
  • this will overwrite all current classes on the body element, how do you prevent that? – user3096443 Jan 07 '14 at 16:09
  • @user3096443: use a regular expression to toggle the class instead (or you could add the class to `document.documentElement` instead. – Andy E Jan 07 '14 at 16:11
  • Where can we see an example of this script being used? – WilliamK Apr 18 '14 at 23:53
  • Today 12-06-14 google doodle also not work if we change the current tab, doodle stops an when we again focus on that tab then it runs. – banny Jun 12 '14 at 04:25
  • @banny: I haven't checked, but it's likely that it's using `requestAnimationFrame()` instead of the Page Visibility API to achieve that. – Andy E Jun 12 '14 at 09:02
  • please move the "var v = 'visible', h = 'hidden', evtMap = { focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h };" outside of the "onchange" function – salexch Jun 26 '14 at 20:17
  • @salexch: why? so it will operate some microseconds faster, because that's your personal preference, or some other pedantic reason? ;-) – Andy E Jun 27 '14 at 08:51
  • a lot of people see this code, It will be nice if it will be clean as possible.. (my opinion). – salexch Jun 28 '14 at 20:28
  • As an improvement, add and remove the class name from the 'body' element's classes, instead of setting the value and overwriting everything. Other code would like to use the class attribute of 'body' too. – HostedMetrics.com Aug 13 '14 at 15:54
  • 4
    @Heliodor: I'd like to keep the code in the answer minimal for now. It was never intended to be a cut-and-paste complete solution, as implementors might want to avoid setting a class on the body and take a completely different action altogether (such as stopping and starting a timer). – Andy E Aug 14 '14 at 08:58
  • 20
    @AndyE Your solution seems to only work if the user changes tabs, or minimizes/maximizes the window. However, the onchange event is not triggered if the user leaves the tab active, but maximizes another program over it from the taskbar. Is there a solution for that scenario? Thanks! – user1491636 Nov 04 '14 at 18:43
  • @user149: sadly there isn't. I consider this to be a bug in the implementations though, so it might be a good idea to vote on them on the respective trackers (the links are in these comments). – Andy E Nov 05 '14 at 00:35
  • @AndyE I think this solution is very good and works well. I've been testing it out and I found a minor issue. I added the following code in the `onchange` function : `if(document.body.className == "visible") $('p').append(document.body.className + ' || ' + new Date().toString() + '');` All it does is output the class name and datetime in a paragraph when the tab is active again. Problem is that the text is being returned twice. Weird thing is that when I set breakpoints in Chrome Dev Tools, the breakpoint is only being fired once so I'm not sure what's the issue. – Daniel Grima Nov 12 '14 at 15:01
  • @AndyE After further testing I've noticed that is only occurs when tabs are switched. If the window is minimised the code I've listed above is only run once. – Daniel Grima Nov 12 '14 at 16:25
  • Why is this function in parentheses? Also how Would I go about using this. Do I just call it once and put whatever code I need to run within the onchange function? Sorry for my lack of knowledge. – Kahless Nov 21 '14 at 01:40
  • @JohnBob: the code is wrapped in an [IIFE](https://www.google.co.uk/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=javascript%20iife), and it's intended to be a starting point for your own implementation. That means you can either put your code in the onchange function, you can leave it as it is (if you want to keep the class change on the body), or you can call your own function at that point. – Andy E Nov 21 '14 at 10:15
  • 1
    Good snippet. But when a window lose `focus`, its `document.visibilityState` value can still be `visible` and its `document.hidden` value can still be `false` in chrome, so `blur !== hidden`. So the `evtMap` in your snippet need more thoughts. – Nimbosa Sep 17 '15 at 08:46
  • Visibility and Focus are not the same thing! For instance, you can tile the windows and lose focus of the web page, while it is still visible. I believe that question's intent was FOCUS, though the author used a fuzzy "active" adjective. The answer above provides the visibility and is, in that respect, wrong (or better said off-topic). – igorludi Apr 19 '16 at 08:36
  • @igorludi: the question might have used the word "focus", but that's not what he was asking for. When you want to switch off or decrease the functionality of intermittently running scripts, you generally don't want to do this whilst the page is still visible. A window can go for a long time without focus whilst still being actively "looked at" by the user—if it is on another monitor or split across the screen, for instance. Yes, my answer falls back to focus but it's not elegant for this exact reason, but covers the majority of cases. – Andy E Apr 20 '16 at 07:46
  • 1
    This is nice, but won't work for example when pressing window key + L to lock a windows pc. – jcbvm Jun 19 '16 at 02:01
  • This works but I discovered a condition where it does not work. On a Mac, if you have two browser windows open side by side with no overlapping of windows, the visibility will not change when you alternate bringing each window into focus. It seems to only work if one of the window that wants to detect visibility changes is partially obscured by some other window. You then need to then tap on the App icon on the Mac toolbar at the bottom of your desktop to switch between windows to get the visibility change event to fire. – Johann Jan 15 '17 at 14:50
  • 1
    @AndroidDev That's because the window is still visible if you have them side by side. This checks for visibility not focus by default. – superphonic May 15 '17 at 15:45
  • I'd prefer `hiddenKey` instead of `hidden`, since it does not describe whether or not anything is hidden, it is merely a string of how to access that property. Also, it would be nice to have a js comment about `this`, something like `//could be window or document, depending on the fallback used`. Thanks though! – dansch May 12 '18 at 20:12
  • this doesn't work in Chrome68 on MacOS. I press Cmd-Tab to switch to another program and no event comes though. The question is is the window **active**, not if it's **visible**. Blur and Focus don't work either – gman Aug 31 '18 at 06:34
  • Also when using two Chrome windows (at least, different profiles under Linux), when the page containing this code is behind, it still thinks it's visible (but it's not). The code from https://stackoverflow.com/a/15846434/909276 works in that scenario – netizen Jan 22 '19 at 09:33
  • Hello this is good answer but you should use the " window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;" block in any case. If not, this method not works if part/any corner of the page is visible. – Fatih Jun 13 '21 at 09:55
  • 1
    a simple solution to check about visibilty is through "mouseleave" event listener on html tag. It will trigger even if user switches from 1 app to another. However, it will also trigger if user goes to browser's menu bar. Another issue is that this still does not work on Safari(is user switches apps), but other than that, its better than visibilitychange – Karanveer Singh Jan 22 '22 at 10:39
143

I would use jQuery because then all you have to do is this:

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

Or at least it worked for me.

Carson Wright
  • 1,519
  • 1
  • 9
  • 2
66

There are 3 typical methods used to determine if the user can see the HTML page, however none of them work perfectly:

  • The W3C Page Visibility API is supposed to do this (supported since: Firefox 10, MSIE 10, Chrome 13). However, this API only raises events when the browser tab is fully overriden (e.g. when the user changes from one tab to another one). The API does not raise events when the visibility cannot be determined with 100% accuracy (e.g. Alt+Tab to switch to another application).

  • Using focus/blur based methods gives you a lot of false positive. For example, if the user displays a smaller window on top of the browser window, the browser window will lose the focus (onblur raised) but the user is still able to see it (so it still need to be refreshed). See also http://javascript.info/tutorial/focus

  • Relying on user activity (mouse move, clicks, key typed) gives you a lot of false positive too. Think about the same case as above, or a user watching a video.

In order to improve the imperfect behaviors described above, I use a combination of the 3 methods: W3C Visibility API, then focus/blur and user activity methods in order to reduce the false positive rate. This allows to manage the following events:

  • Changing browser tab to another one (100% accuracy, thanks to the W3C Page Visibility API)
  • Page potentially hidden by another window, e.g. due to Alt+Tab (probabilistic = not 100% accurate)
  • User attention potentially not focused on the HTML page (probabilistic = not 100% accurate)

This is how it works: when the document lose the focus, the user activity (such as mouse move) on the document is monitored in order to determine if the window is visible or not. The page visibility probability is inversely proportional to the time of the last user activity on the page: if the user makes no activity on the document for a long time, the page is most probably not visible. The code below mimics the W3C Page Visibility API: it behaves the same way but has a small false positive rate. It has the advantage to be multibrowser (tested on 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>

Since there is currently no working cross-browser solution without false positive, you should better think twice about disabling periodical activity on your web site.

Madacol
  • 3,611
  • 34
  • 33
Julien Kronegg
  • 4,968
  • 1
  • 47
  • 60
  • Wouldn't using a strict comparison operator on the string 'undefined' instead of the undefined keyword cause false positives in the above code? – Jonathon Jul 16 '16 at 00:23
  • @kiran: Actually it IS working with Alt+Tab. You cannot determine if the page is hidden when you do a Alt+Tab because you may switch to a smaller window so you can't guarantee that your page is fully hidden. This is why I use the notion of "potentially hidden" (in the example, the threshold is set to 4 seconds, so you need to switch to another window using Alt+Tab for at least 4 seconds). However your comment shows that the answer was not so clear, so I reworded it. – Julien Kronegg Dec 01 '16 at 21:14
  • @JulienKronegg I think this is the best solution yet. However, the code above extremely needs some refactoring and abstractions. Why don't you upload it to GitHub and let the community refactoring it? – Jacob Apr 20 '17 at 11:24
  • 2
    @Jacob I'm happy you liked my solution. Feel free to promote it into a GitHub project by yourself. I give the code with licence Creative Commons BY https://creativecommons.org/licenses/by/4.0/ – Julien Kronegg Apr 20 '17 at 14:17
  • "For example, if the user displays a smaller window on top of the browser window, the browser window will lose the focus" Are you talking about iframes, if so that's true. – Caleb Taylor Sep 13 '21 at 21:14
  • 1
    @Caleb no, I'm talking about another application being in front of the web page (e.g. calculator). In this case, the web page looses the focus, but is still able to receive some events (e.g. mouse over events). – Julien Kronegg Sep 15 '21 at 05:04
38

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

l2aelba
  • 21,591
  • 22
  • 102
  • 138
  • The question is not about page visibility. It's about not active / active – gman Aug 31 '18 at 07:24
  • I think OP is not talking about ide's function – l2aelba Aug 31 '18 at 07:42
  • 2
    I'm not talking about ide's either. I'm talking about alt-tabbing/cmd-tabbing to another app. Suddenly the page is not active. The page visibility api does not help me know if the page is not active, it only helps me know if the is possibly not visible. – gman Aug 31 '18 at 08:23
30

I started off using the community wiki answer, but realised that it wasn't detecting alt-tab events in Chrome. This is because it uses the first available event source, and in this case it's the page visibility API, which in Chrome seems to not track alt-tabbing.

I decided to modify the script a bit to keep track of all possible events for page focus changes. Here's a function you can drop in:

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

Use it like this:

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

This version listens for all the different visibility events and fires a callback if any of them causes a change. The focused and unfocused handlers make sure that the callback isn't called multiple times if multiple APIs catch the same visibility change.

Luis Lobo
  • 489
  • 4
  • 7
Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57
  • 1
    Chrome for example has both `document.hidden` and `document.webkitHidden`. Without the `else` in the `if` construction we would get 2 callback calls right? – Christiaan Westerbeek Aug 23 '18 at 15:28
  • @ChristiaanWesterbeek That's a good point, I didn't think of that! If you can edit this post go ahead and I'll accept :) – Daniel Buckmaster Aug 27 '18 at 01:14
  • Uh hey wait a minute: the edit to add "else"s suggested by ChristiaanWesterbeek and actually added by @1.21Gigawatts does not seem like a good idea: it defeats the original purchase of Daniel 's idea, which is to try all the supported methods in parallel. And there is no risk of the callback being called twice because focused() and unfocused() suppress extra calls when nothing is changing. Really seems like we should revert to the first rev. – Louis Semprini Jun 16 '19 at 03:35
  • @LouisSemprini that's a great catch. I had forgotten the original intent of the code! I've restored the original and added an explanation! – Daniel Buckmaster Jun 27 '19 at 07:36
  • 1
    checking this as of today, it does not detect alt+tab at least on Chrome 78 + macos – Hugo Gresse Dec 11 '19 at 17:07
  • 1
    @HugoGresse this snippet works perfectly fine on Chrome + MacOS. – Cory Robinson Feb 10 '20 at 14:30
  • 1
    @DanielBuckmaster This still has the (non-controlled) iframe problem, and additionally has the problem that it doesn't work until the user has performed some kind of action. The code runs and the events get wired up, but they don't get fired for alt+tab until some kind of active event (tab over elements, click on the page, etc.) happens. This means if you load a page and then alt+tab without any page action, you don't get the unfocused event when you tab away, nor the focused when you come back. – AntonOfTheWoods Jan 24 '23 at 10:33
28

There is a neat library available on GitHub:

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

Example:

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

I've tested version 1.0.1 on all browsers I have and can confirm that it works with:

  • IE9, IE10
  • FF 26.0
  • Chrome 34.0

... and probably all newer versions.

Doesn't fully work with:

  • IE8 - always indicate that tab/window is currently active (.now() always returns true for me)
omnomnom
  • 8,911
  • 4
  • 41
  • 50
  • 1
    Accepted answer caused issues in IE9. This library works great. – Tom Teman Jan 14 '15 at 15:59
  • 2
    This library is completely abandoned. While it looks like it has a typescript version, it doesn't work in VSCode any more and even copy/pasting the source has lots of stuff that is no longer considered good practice for typescript – AntonOfTheWoods Feb 15 '22 at 23:59
24

I create a Comet Chat for my app, and when I receive a message from another user I use:

if(new_message){
    if(!document.hasFocus()){
        audio.play();
        document.title="Have new messages";
    }
    else{
        audio.stop();
        document.title="Application Name";
    } 
}
Grey Li
  • 11,664
  • 4
  • 54
  • 64
infinito84
  • 1,971
  • 17
  • 8
  • 3
    The cleanest solution with support back to IE6 – Paul Cooper Mar 03 '15 at 09:34
  • 8
    `document.hasFocus()` is the cleanest way to do it. All the other ways using the visibility api or event based or looking for various levels of user activity/lack of activity become overcomplicated and full of edge cases and holes. put it on a simple interval and raise a custom event when the results change. Example: https://jsfiddle.net/59utucz6/1/ – danatcofo Mar 29 '17 at 18:17
  • 1
    Efficient, and unlike the other solutions gives correct feedback when you switch to another browser tab or window, and even a different application. – ow3n Jul 17 '17 at 09:20
  • No Doubt,its the cleanest way,but it doesn't work in firefox – hardik chugh Jun 07 '19 at 16:44
  • 1
    If I open Chrome Dev tools then document.hasFocus() equals to false. Or even if you click on the top panel of the browser, same happens. I'm not sure this solution is suitable to pause video, animation, etc – tylik Jul 10 '19 at 15:05
  • @tylik , the document won't have focus while you're in the dev tools. Paste this into the JS console, and then click on the document, the output should change from `false` to `true`. Code: `setInterval(() => { console.log(document.hasFocus()) }, 1000);`, – Walter Stabosz Oct 27 '21 at 15:02
9

This is really tricky. There seems to be no solution given the following requirements.

  • The page includes iframes that you have no control over
  • You want to track visibility state change regardless of the change being triggered by a TAB change (ctrl+tab) or a window change (alt+tab)

This happens because:

  • The page Visibility API can reliably tell you of a tab change (even with iframes), but it can't tell you when the user changes windows.
  • Listening to window blur/focus events can detect alt+tabs and ctrl+tabs, as long as the iframe doesn't have focus.

Given these restrictions, it is possible to implement a solution that combines - The page Visibility API - window blur/focus - document.activeElement

That is able to:

  • 1) ctrl+tab when parent page has focus: YES
  • 2) ctrl+tab when iframe has focus: YES
  • 3) alt+tab when parent page has focus: YES
  • 4) alt+tab when iframe has focus: NO <-- bummer

When the iframe has focus, your blur/focus events don't get invoked at all, and the page Visibility API won't trigger on alt+tab.

I built upon @AndyE's solution and implemented this (almost good) solution here: https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html (sorry, I had some trouble with JSFiddle).

This is also available on Github: https://github.com/qmagico/estante-components

This works on chrome/chromium. It kind works on firefox, except that it doesn't load the iframe contents (any idea why?)

Anyway, to resolve the last problem (4), the only way you can do that is to listen for blur/focus events on the iframe. If you have some control over the iframes, you can use the postMessage API to do that.

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

I still haven't tested this with enough browsers. If you can find more info about where this doesn't work, please let me know in the comments below.

Tony Lâmpada
  • 5,301
  • 6
  • 38
  • 50
9

this works for me on chrome 67, firefox 67,

if(!document.hasFocus()) {
    // do stuff
}
Samad
  • 1,776
  • 2
  • 20
  • 35
8
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/

yckart
  • 32,460
  • 9
  • 122
  • 129
8

this worked for me

document.addEventListener("visibilitychange", function() {
      document.title = document.hidden ? "I'm away" : "I'm here";
});

demo: https://iamsahilralkar.github.io/document-hidden-demo/

Sahil Ralkar
  • 2,331
  • 23
  • 25
8

This works in all modern browsers:

  • when changing tabs
  • when changing windows(Alt+Tab)
  • when maximizing another program from the taskbar
var eventName;
var visible = true;
var propName = "hidden";
if (propName in document) eventName = "visibilitychange";
else if ((propName = "msHidden") in document) eventName = "msvisibilitychange";
else if ((propName = "mozHidden") in document) eventName = "mozvisibilitychange";
else if ((propName = "webkitHidden") in document) eventName = "webkitvisibilitychange";
if (eventName) document.addEventListener(eventName, handleChange);

if ("onfocusin" in document) document.onfocusin = document.onfocusout = handleChange; //IE 9
window.onpageshow = window.onpagehide = window.onfocus = window.onblur = handleChange;// Changing tab with alt+tab

// Initialize state if Page Visibility API is supported
if (document[propName] !== undefined) handleChange({ type: document[propName] ? "blur" : "focus" });

function handleChange(evt) {
  evt = evt || window.event;
  if (visible && (["blur", "focusout", "pagehide"].includes(evt.type) || (this && this[propName]))){
    visible = false;
    console.log("Out...")
  }
  else if (!visible && (["focus", "focusin", "pageshow"].includes(evt.type) || (this && !this[propName]))){
    visible = true;
    console.log("In...")
  }
}
adrihanu
  • 1,249
  • 2
  • 16
  • 28
  • 1
    This works super well, even when opening devtools it triggers it, great solution! – Haim Jan 23 '23 at 15:41
5

In HTML 5 you could also use:

  • onpageshow: Script to be run when the window becomes visible
  • onpagehide: Script to be run when the window is hidden

See:

sim642
  • 751
  • 7
  • 14
roberkules
  • 6,557
  • 2
  • 44
  • 52
  • 3
    I think this is related to the BFCache: when the user clicks Back or Forward -- it is not related to the page being at the top of computer desktop. – nonopolarity Jul 11 '13 at 07:07
4

u can use :

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

})();
maryam
  • 584
  • 9
  • 26
3

A slightly more complicated way would be to use setInterval() to check mouse position and compare to last check. If the mouse hasn't moved in a set amount of time, the user is probably idle.

This has the added advantage of telling if the user is idle, instead of just checking if the window is not active.

As many people have pointed out, this is not always a good way to check whether the user or browser window is idle, as the user might not even be using the mouse or is watching a video, or similar. I am just suggesting one possible way to check for idle-ness.

Austin Hyde
  • 26,347
  • 28
  • 96
  • 129
  • 32
    Unless the user doesn't have a mouse. – user1686 Jun 29 '09 at 19:36
  • @Annan: It's http://www.codinghorror.com/blog/2007/03/going-commando---put-down-the-mouse.html now. – chiborg Jun 16 '11 at 14:34
  • This also doesn't play dice if the user is watching a video – jamiew Nov 29 '11 at 17:32
  • you could use onkeypress or other similar events to reset the timer and solve the non-mouse issue. Of course it still wouldn't work for users actively looking at the page to watch a video, study an image, etc. – joshuahedlund Jan 10 '12 at 21:34
3

This is an adaptation of the answer from Andy E.

This will do a task e.g. refresh the page every 30 seconds, but only if the page is visible and focused.

If visibility can't be detected, then only focus will be used.

If the user focuses the page, then it will update immediately

The page won't update again until 30 seconds after any ajax call

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);
}
brasofilo
  • 25,496
  • 15
  • 91
  • 179
roger
  • 71
  • 2
3

For a solution without jQuery check out Visibility.js which provides information about three page states

visible    ... page is visible
hidden     ... page is not visible
prerender  ... page is being prerendered by the browser

and also convenience-wrappers for 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();
});

A fallback for older browsers (IE < 10; iOS < 7) is also available

Niko
  • 574
  • 5
  • 13
3

The Chromium team is currently developing the Idle Detection API. It is available as an origin trial since Chrome 88, which is already the 2nd origin trial for this feature. An earlier origin trial went from Chrome 84 through Chrome 86.

It can also be enabled via a flag:

Enabling via chrome://flags

To experiment with the Idle Detection API locally, without an origin trial token, enable the #enable-experimental-web-platform-features flag in chrome://flags.

A demo can be found here:

https://idle-detection.glitch.me/

It has to be noted though that this API is permission-based (as it should be, otherwise this could be misused to monitor a user's behaviour!).

connexo
  • 53,704
  • 14
  • 91
  • 128
2

For angular.js, here is a directive (based on the accepted answer) that will allow your controller to react to a change in visibility:

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

});

You can use it like this example: <div react-on-window-focus="refresh()">, where refresh() is a scope function in the scope of whatever Controller is in scope.

Steve Campbell
  • 3,385
  • 1
  • 31
  • 43
2

Simple/immediate check:

if(document.hidden) {
  // do something
}

Visibility change event:

document.addEventListener("visibilitychange", function() {
  console.log(document.visibilityState); // "hidden" or "visible"
}, false);

Promise-based event:

// An `await`able function that resolves when page visibility changes:
function visibilityChange(state="") {
  return new Promise(resolve => {
    document.addEventListener("visibilitychange", function() {
      if(!state || document.visibilityState === state) { 
        resolve(document.visibilityState);
        document.removeEventListener("visibilitychange", arguments.callee);
      }
    });
  });
}

// Use it like this:
await visibilityChange();
console.log(document.visibilityState);

// Or wait for page to become...
await visibilityChange("visible");
await visibilityChange("hidden");

(Note: I was the one who added the latter two solutions into this answer, since that question is now closed and I couldn't add my own answer. Just in case someone thinks I've copied them from that post without crediting.)

joe
  • 3,752
  • 1
  • 32
  • 41
1

If you want to act on whole browser blur: As I commented, if browser lose focus none of the suggested events fire. My idea is to count up in a loop and reset the counter if an event fire. If the counter reach a limit I do a location.href to an other page. This also fire if you work on dev-tools.

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

This is a draft successful tested on FF.

Luke H
  • 3,125
  • 27
  • 31
B.F.
  • 477
  • 6
  • 9
1

I reread the @daniel-buckmaster version I didn't make the multiple attempt, however, the code seems more elegant to me...

// on-visibility-change.js v1.0.1, based on https://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active#38710376
function onVisibilityChange(callback) {
    let d = document;
    let visible = true;
    let prefix;
    if ('hidden' in d) {
        prefix = 'h';
    } else if ('webkitHidden' in d) {
        prefix = 'webkitH';
    } else if ('mozHidden' in d) {
        prefix = 'mozH';
    } else if ('msHidden' in d) {
        prefix = 'msH';
    } else if ('onfocusin' in d) { // ie 9 and lower
        d.onfocusin = focused;
        d.onfocusout = unfocused;
    } else { // others
        window.onpageshow = window.onfocus = focused;
        window.onpagehide = window.onblur = unfocused;
    };
    if (prefix) {
        visible = !d[prefix + 'idden'];
        d.addEventListener(prefix.substring(0, prefix.length - 1) + 'visibilitychange', function() {
            (d[prefix + 'idden'] ? unfocused : focused)();
        });
    };

    function focused() {
        if (!visible) {
            callback(visible = true);
        };
    };

    function unfocused() {
        if (visible) {
            callback(visible = false);
        };
    };
};
Luis Lobo
  • 489
  • 4
  • 7
0

Here is a solid, modern solution. (Short a sweet )

document.addEventListener("visibilitychange", () => {
  console.log( document.hasFocus() )
})

This will setup a listener to trigger when any visibility event is fired which could be a focus or blur.

Cory Robinson
  • 4,616
  • 4
  • 36
  • 53
0

my code

let browser_active = ((typeof document.hasFocus != 'undefined' ? document.hasFocus() : 1) ? 1 : 0);
if (!browser_active) {
 // active
} 
Akbarali
  • 688
  • 7
  • 16
0

If you want to do this in React with a piece of state, here's an implementation:


import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [tabActive, setTabActive] = useState(true);

  useEffect(() => {
    const handleVisibilityChange = () => {
      setTabActive(!document.hidden);
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);

    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, []);

  // your component code here
}

Allan of Sydney
  • 1,410
  • 14
  • 23