208

Is there a reliable cross-browser way to detect that a tab has focus.

The scenario is that we have an application that polls regularly for stock prices, and if the page doesn't have focus we could stop the polling and save everyone the traffic noise, especially as people are fans of opening several tabs with different portfolios.

Is window.onblur and window.onfocus an option for this?

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • 3
    possible duplicate of [Is there a way to detect if a browser window is not currently active?](http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active) – lucian Feb 02 '14 at 01:43
  • Yes those should work for you. You just reminded me of this link I came across that exploits those techniques. [interesting read](http://www.azarask.in/blog/post/a-new-type-of-phishing-attack/) – Brian Glaz Sep 12 '11 at 14:26
  • 1
    The top answers here are outdated, use an EventListener for 'visibilitychange' (see below: https://stackoverflow.com/a/69535061/1066234) – Avatar Mar 14 '23 at 06:08

9 Answers9

162

Yes, window.onfocus and window.onblur should work for your scenario.

Luke Stevenson
  • 10,357
  • 2
  • 26
  • 41
Ryan Wright
  • 3,333
  • 2
  • 16
  • 9
  • 4
    The onfocusin/onfocusout aspect of this, and also the note about telling the user you have paused are really good notes. Thanks. – Fenton Sep 12 '11 at 14:30
  • 9
    Please note that you cannot distinguish between the page being active or inactive at page load this way. – pimvdb Sep 12 '11 at 14:35
  • @SteveFenton - `onfocus` is crossbrowser, where the events you've mentioned are IE-only, I can't see why this would be considered a good note by you.. – vsync May 10 '14 at 22:13
  • 2
    @vsync - read the linked article, you'll see it uses *both* 'onfocusin' and 'onfocus'. – Fenton May 11 '14 at 09:02
  • Could you at least mention the difference between the two? – Lenar Hoyt Jul 05 '16 at 16:04
  • Please note that this is not working correctly when working with iframes! – Karmidzhanov Oct 09 '18 at 08:21
  • if you open developer console window and click on it, the tab loses focus. – Shoyeb Sheikh Oct 12 '21 at 04:15
  • this should not be the accepted nor top-answer to the question "if tab is on focus", becauser "if (window.onfocus)" does not return the answer OP is asking for. It's not the wrong answer, but for a beginner this may be confusing. – n.r. Jul 12 '23 at 08:12
98

Surprising to see nobody mentioned document.hasFocus

if (document.hasFocus()) console.log('Tab is active')

MDN has more information.

aleclarson
  • 18,087
  • 14
  • 64
  • 91
  • works for me (tested on Chrome and Firefox). The accepted answer (onfocus/onblur) did _not_ work – harmv Jan 09 '18 at 13:14
  • 5
    The correct answer yet again at the very bottom. Way to go StackOverflow! – October Eleven Apr 04 '19 at 11:36
  • really, isn't this the perfect answer? does anyone see any downside? – gaspar Aug 06 '19 at 08:23
  • 3
    The only downside to this is that if you're trying to determine if tab is in focus from within an iframe, then it would fail in case the iframe was loaded when the parent page was still out of focus. To cover that as well you'll have to go with the page visibility api. – Ivan Jan 27 '20 at 08:16
  • 3
    if you just click on tab header, document.hasFocus() value won't change, as it is on document, you have to click on document to see the change. it is a downside. – Satyanarayana.Ruppa Jun 07 '21 at 08:50
  • 1
    @Satyanarayana.Ruppa In my tests just now, clicking on a tab brings the document into focus (tested in Chrome and Firefox) - even if e.g. previously the focus was on DevTools/console the last time you were using that tab. – joe Feb 02 '22 at 04:28
  • This should be the accepted answer now, in 2022 at least. I just tested in Firefox, Chrome, and Safari. Works great in all! Thanks. – Bryan Guillen May 26 '22 at 11:31
78

Important Edit: This answer is outdated. Since writing it, the Visibility API (mdn, example, spec) has been introduced. It is the better way to solve this problem.


var focused = true;

window.onfocus = function() {
    focused = true;
};
window.onblur = function() {
    focused = false;
};

AFAIK, focus and blur are all supported on...everything. (see http://www.quirksmode.org/dom/events/index.html )

Zirak
  • 38,920
  • 13
  • 81
  • 92
  • 2
    Just a little note, with all of these solutions, you run the risk of the user changing tabs before the javascript is fully loaded, thus assigning the wrong value to focused. Not sure there is a good way around it. – JayD3e Dec 31 '13 at 19:10
  • The update links are exactly what I was looking for. Thanks for adding them! – webLacky3rdClass Apr 15 '15 at 21:33
  • The question is specifically about detecting whether a page has focus, which is different from detecting whether the page is visible. Multiple pages can be visible at the same time (in different windows), while only one can have focus. Use whichever technique suits your needs, but know the difference. – jaredjacobs Sep 19 '18 at 16:47
  • 2
    This is a dangerous solution because it runs the risk of overriding some other event listener in a larger application. You should instead follow this answer: https://stackoverflow.com/a/21935031/549503 – mmmeff Sep 21 '18 at 22:32
67

While searching about this problem, I found a recommendation that Page Visibility API should be used. Most modern browsers support this API according to Can I Use: http://caniuse.com/#feat=pagevisibility.

Here's a working example (derived from this snippet):

$(document).ready(function() {
  var hidden, visibilityState, visibilityChange;

  if (typeof document.hidden !== "undefined") {
    hidden = "hidden", visibilityChange = "visibilitychange", visibilityState = "visibilityState";
  } else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden", visibilityChange = "msvisibilitychange", visibilityState = "msVisibilityState";
  }

  var document_hidden = document[hidden];

  document.addEventListener(visibilityChange, function() {
    if(document_hidden != document[hidden]) {
      if(document[hidden]) {
        // Document hidden
      } else {
        // Document shown
      }

      document_hidden = document[hidden];
    }
  });
});

Update: The example above used to have prefixed properties for Gecko and WebKit browsers, but I removed that implementation because these browsers have been offering Page Visibility API without a prefix for a while now. I kept Microsoft specific prefix in order to stay compatible with IE10.

Ilija
  • 4,105
  • 4
  • 32
  • 46
  • When the vendor prefixes go from this, I'll probably switch! – Fenton Feb 21 '14 at 20:14
  • Only real problem with this are not vendor prefixes because there's an official W3C recommendation (dated 29. October 2013). What is a problem in some cases is that page visibility API is supported in IE10 and newer. If you need to support IE9, you should look for a different approach… – Ilija Feb 22 '14 at 16:58
  • This is the correct way to do it for all modern browsers. +1 – Ajedi32 Jan 26 '15 at 15:18
  • Are you sure these vendor prefixes are even necessary? According to MDN and CanIUse, they haven't been necessary on Chrome since version 32, or on Firefox since version 17, and they've never been necessary on IE. – Ajedi32 Jan 26 '15 at 15:23
  • @Ajedi32 Thanks. I'll need to do some tests and digging to see what it still relevant, and what can be left out now. – Ilija Jan 28 '15 at 10:43
  • The question is specifically about detecting whether a page has focus, which is different from detecting whether the page is visible. Multiple pages can be visible at the same time (in different windows), while only one can have focus. Use whichever technique suits your needs, but know the difference. – jaredjacobs Sep 19 '18 at 16:50
  • visibilityState variable is unused in the example – Trevor May 10 '19 at 14:24
  • The event does not fire in Firefox when FF itself (not just the tab) loses focus. – Quuzuu Aug 04 '21 at 19:31
20

Posting this answer because I found a bug in accepted answer.

The bug is when you open a developer console on the focused window and click anywhere on it, the developer console has the focus now, at this point window.onfocus or window.onblur has no effect whatsoever.

So here is my solution,

document.addEventListener("visibilitychange", function() {
    if (document.visibilityState === 'visible') {
        console.log('has focus');
    } else {
        console.log('lost focus');
    }
});

Read more https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event

Shoyeb Sheikh
  • 2,659
  • 2
  • 10
  • 19
  • 1
    confirming `document.visiblitychange` worked as intended while debugging with developer console open while `window.blur` `window.focus` did not – eballeste Dec 24 '22 at 18:30
8

I would do it this way (Reference http://www.w3.org/TR/page-visibility/):

    window.onload = function() {

        // check the visiblility of the page
        var hidden, visibilityState, visibilityChange;

        if (typeof document.hidden !== "undefined") {
            hidden = "hidden", visibilityChange = "visibilitychange", visibilityState = "visibilityState";
        }
        else if (typeof document.mozHidden !== "undefined") {
            hidden = "mozHidden", visibilityChange = "mozvisibilitychange", visibilityState = "mozVisibilityState";
        }
        else if (typeof document.msHidden !== "undefined") {
            hidden = "msHidden", visibilityChange = "msvisibilitychange", visibilityState = "msVisibilityState";
        }
        else if (typeof document.webkitHidden !== "undefined") {
            hidden = "webkitHidden", visibilityChange = "webkitvisibilitychange", visibilityState = "webkitVisibilityState";
        }


        if (typeof document.addEventListener === "undefined" || typeof hidden === "undefined") {
            // not supported
        }
        else {
            document.addEventListener(visibilityChange, function() {
                console.log("hidden: " + document[hidden]);
                console.log(document[visibilityState]);

                switch (document[visibilityState]) {
                case "visible":
                    // visible
                    break;
                case "hidden":
                    // hidden
                    break;
                }
            }, false);
        }

        if (document[visibilityState] === "visible") {
            // visible
        }

    };  
Michael
  • 32,527
  • 49
  • 210
  • 370
  • Can you explain how this answer differs from the answer given by @Ilija - there may be a difference, but it is subtle - so an explanation of what it is and why it should be different would be appreciated. – Fenton Sep 03 '14 at 08:23
  • Thanks @confile! The code is self explanatory and easy to use. Implemented this as a function and it works like a charm - cross-browser and cross-platform, including mobile browsers! – GTodorov Apr 26 '21 at 14:55
2

Cross Browser jQuery Solution! Raw available at GitHub

Fun & Easy to Use!

The following plugin will go through your standard test for various versions of IE, Chrome, Firefox, Safari, etc.. and establish your declared methods accordingly. It also deals with issues such as:

  • onblur|.blur/onfocus|.focus "duplicate" calls
  • window losing focus through selection of alternate app, like word
    • This tends to be undesirable simply because, if you have a bank page open, and it's onblur event tells it to mask the page, then if you open calculator, you can't see the page anymore!
  • Not triggering on page load

Use is as simple as: Scroll Down to 'Run Snippet'

$.winFocus(function(event, isVisible) {
    console.log("Combo\t\t", event, isVisible);
});

//  OR Pass False boolean, and it will not trigger on load,
//  Instead, it will first trigger on first blur of current tab_window
$.winFocus(function(event, isVisible) {
    console.log("Combo\t\t", event, isVisible);
}, false);

//  OR Establish an object having methods "blur" & "focus", and/or "blurFocus"
//  (yes, you can set all 3, tho blurFocus is the only one with an 'isVisible' param)
$.winFocus({
    blur: function(event) {
        console.log("Blur\t\t", event);
    },
    focus: function(event) {
        console.log("Focus\t\t", event);
    }
});

//  OR First method becoms a "blur", second method becoms "focus"!
$.winFocus(function(event) {
    console.log("Blur\t\t", event);
},
function(event) {
    console.log("Focus\t\t", event);
});

/*    Begin Plugin    */
;;(function($){$.winFocus||($.extend({winFocus:function(){var a=!0,b=[];$(document).data("winFocus")||$(document).data("winFocus",$.winFocus.init());for(x in arguments)"object"==typeof arguments[x]?(arguments[x].blur&&$.winFocus.methods.blur.push(arguments[x].blur),arguments[x].focus&&$.winFocus.methods.focus.push(arguments[x].focus),arguments[x].blurFocus&&$.winFocus.methods.blurFocus.push(arguments[x].blurFocus),arguments[x].initRun&&(a=arguments[x].initRun)):"function"==typeof arguments[x]?b.push(arguments[x]):
"boolean"==typeof arguments[x]&&(a=arguments[x]);b&&(1==b.length?$.winFocus.methods.blurFocus.push(b[0]):($.winFocus.methods.blur.push(b[0]),$.winFocus.methods.focus.push(b[1])));if(a)$.winFocus.methods.onChange()}}),$.winFocus.init=function(){$.winFocus.props.hidden in document?document.addEventListener("visibilitychange",$.winFocus.methods.onChange):($.winFocus.props.hidden="mozHidden")in document?document.addEventListener("mozvisibilitychange",$.winFocus.methods.onChange):($.winFocus.props.hidden=
"webkitHidden")in document?document.addEventListener("webkitvisibilitychange",$.winFocus.methods.onChange):($.winFocus.props.hidden="msHidden")in document?document.addEventListener("msvisibilitychange",$.winFocus.methods.onChange):($.winFocus.props.hidden="onfocusin")in document?document.onfocusin=document.onfocusout=$.winFocus.methods.onChange:window.onpageshow=window.onpagehide=window.onfocus=window.onblur=$.winFocus.methods.onChange;return $.winFocus},$.winFocus.methods={blurFocus:[],blur:[],focus:[],
exeCB:function(a){$.winFocus.methods.blurFocus&&$.each($.winFocus.methods.blurFocus,function(b,c){this.apply($.winFocus,[a,!a.hidden])});a.hidden&&$.winFocus.methods.blur&&$.each($.winFocus.methods.blur,function(b,c){this.apply($.winFocus,[a])});!a.hidden&&$.winFocus.methods.focus&&$.each($.winFocus.methods.focus,function(b,c){this.apply($.winFocus,[a])})},onChange:function(a){var b={focus:!1,focusin:!1,pageshow:!1,blur:!0,focusout:!0,pagehide:!0};if(a=a||window.event)a.hidden=a.type in b?b[a.type]:
document[$.winFocus.props.hidden],$(window).data("visible",!a.hidden),$.winFocus.methods.exeCB(a);else try{$.winFocus.methods.onChange.call(document,new Event("visibilitychange"))}catch(c){}}},$.winFocus.props={hidden:"hidden"})})(jQuery);
/*    End Plugin      */

// Simple example
$(function() {
 $.winFocus(function(event, isVisible) {
  $('td tbody').empty();
  $.each(event, function(i) {
   $('td tbody').append(
    $('<tr />').append(
     $('<th />', { text: i }),
     $('<td />', { text: this.toString() })
    )
   )
  });
  if (isVisible) 
   $("#isVisible").stop().delay(100).fadeOut('fast', function(e) {
    $('body').addClass('visible');
    $(this).stop().text('TRUE').fadeIn('slow');
   });
  else {
   $('body').removeClass('visible');
   $("#isVisible").text('FALSE');
  }
 });
})
body { background: #AAF; }
table { width: 100%; }
table table { border-collapse: collapse; margin: 0 auto; width: auto; }
tbody > tr > th { text-align: right; }
td { width: 50%; }
th, td { padding: .1em .5em; }
td th, td td { border: 1px solid; }
.visible { background: #FFA; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h3>See Console for Event Object Returned</h3>
<table>
    <tr>
        <th><p>Is Visible?</p></th>
        <td><p id="isVisible">TRUE</p></td>
    </tr>
    <tr>
        <td colspan="2">
            <table>
                <thead>
                    <tr>
                        <th colspan="2">Event Data <span style="font-size: .8em;">{ See Console for More Details }</span></th>
                    </tr>
                </thead>
                <tbody></tbody>
            </table>
        </td>
    </tr>
</table>
SpYk3HH
  • 22,272
  • 11
  • 70
  • 81
  • You should put the code un-minimized for the plugin. – Patrick Desjardins Oct 20 '15 at 16:54
  • @PatrickDesjardins yeah. Plan on doing this this weekend along with other things. I? Make a gist for a bunch of stuff I have. Jdmckinstry at github. Will add links to old answers like these as I get them added to gist – SpYk3HH Oct 20 '15 at 17:10
  • What if I want the page to lose focus, when I switch to another app, like "Word" or "Calculator"? – Benas Dec 30 '15 at 15:27
  • @Benas Could be wrong, but I do believe that is the base functionality of the very basic `jQuery(window).blur/focus`, which was undesired by many, thus one of the reasons I made this plugin. The plugin is meant to help provide what jQuery doesn't already – SpYk3HH Dec 30 '15 at 15:51
1

This is 10 years old. Here is a newer version: Pause/resume CSS animations when switching tabs

Basicly use Mozillas Javascript https://developer.mozilla.org/en-US/docs/Web/API/Window/focus_event

 function pause() {

 //Do something

 }

 function play() {

 //Do something

 }
 window.addEventListener('blur', pause);
 window.addEventListener('focus', play);
human
  • 467
  • 4
  • 6
0

A React hook version based on all these fine gentlemen's answers here above:

import { useEffect, useState } from 'react';

const EVENT_NAME = 'visibilitychange';

export const useTabFocus = () => {
    const [hasFocus, setHasFocus] = useState(isVisible());

    useEffect(() => {
        const onVisibilityChange = () => {
            setHasFocus(isVisible());
        };

        document.addEventListener(EVENT_NAME, onVisibilityChange);

        return () => {
            document.removeEventListener(EVENT_NAME, onVisibilityChange);
        };
    }, []);

    return hasFocus;
};

const isVisible = () => document.visibilityState === 'visible';

Guy
  • 12,488
  • 16
  • 79
  • 119