Есть ли способ определить, не открыто ли окно браузера?

У меня есть JavaScript, который периодически выполняет свою деятельность. Когда пользователь не смотрит на сайт (т.е. Окно или вкладка не имеют фокуса), было бы неплохо не работать.

Есть ли способ сделать это с помощью JavaScript?

Моя контрольная точка: Gmail Чат воспроизводит звук, если используемое вами окно неактивно.

Ответ 1

С момента написания этого ответа новая спецификация достигла статуса рекомендации благодаря W3C. API видимости страницы (на MDN) теперь позволяет нам более точно определять, когда страница скрыта для пользователя.

document.addEventListener("visibilitychange", onchange);

Текущая поддержка браузера:

  • Chrome 13+
  • Internet Explorer 10+
  • Firefox 10+
  • Опера 12. 10+ [read notes]

Следующий код возвращается к менее надежному методу размытия/фокусировки в несовместимых браузерах:

(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 и onfocusout требуются для IE 9 и ниже, тогда как все остальные используют onfocus и onblur, кроме iOS, которая использует onpageshow и onpagehide.

Ответ 2

Я бы использовал jQuery, потому что тогда все, что вам нужно сделать, это следующее:

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

Или, по крайней мере, это сработало для меня.

Ответ 3

Существует три типичных метода, используемых для определения того, может ли пользователь видеть HTML-страницу, однако ни одна из них не работает отлично:

  • W3C API видимости страницы должен делать это (поддерживается с: Firefox 10, MSIE 10, Chrome 13). Однако этот API вызывает только события, когда вкладка браузера полностью переопределяется (например, когда пользователь переходит с одной вкладки на другую). API не создает события, когда видимость не может быть определена с 100% -ной точностью (например, Alt + Tab для переключения в другое приложение).

  • Использование методов фокуса/размытия дает вам много ложных срабатываний. Например, если пользователь отображает меньшее окно поверх окна браузера, окно браузера потеряет фокус (onblur поднят), но пользователь все еще может его увидеть (так что его еще нужно обновить). См. Также http://javascript.info/tutorial/focus

  • Опираясь на активность пользователя (перемещение мыши, клики, ввод строки) также дает вам много ложных срабатываний. Подумайте о том же случае, что и выше, или о пользователе, просматривающем видео.

Чтобы улучшить несовершенное поведение, описанное выше, я использую комбинацию из трех методов: API видимости W3C, затем фокус/размытие и методы активности пользователя, чтобы уменьшить ложную положительную скорость. Это позволяет управлять следующими событиями:

  • Изменение вкладки браузера на другую (100% -ная точность, благодаря API-интерфейсу видимости страницы W3C)
  • Страница потенциально скрыта другим окном, например. из-за Alt + Tab (вероятностный = не на 100% точный)
  • Внимание пользователя не сосредоточено на HTML-странице (вероятностный = не на 100% точный)

Вот как это работает: когда документ потеряет фокус, отслеживается активность пользователя (например, перемещение мыши) в документе, чтобы определить, видимо ли это окно. Вероятность видимости страницы обратно пропорциональна времени последнего действия пользователя на странице: если пользователь долгое время не выполняет никаких действий над документом, эта страница, скорее всего, не видна. Приведенный ниже код имитирует API видимости страниц W3C: он ведет себя одинаково, но имеет небольшую ложную положительную оценку. Он имеет преимущество быть мультибраузером (тестируется на 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>

Поскольку в настоящее время нет рабочего кроссбраузерного решения без ложных срабатываний, вам следует подумать дважды о отключении периодической активности на вашем веб-сайте.

Ответ 4

В GitHub есть чистая библиотека:

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

Пример:

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

Я тестировал версию 1.0.1 во всех браузерах, которые у меня есть, и могу подтвердить, что он работает с:

  • IE9, IE10
  • FF 26.0
  • Chrome 34.0

... и, возможно, все более новые версии.

Не работает полностью:

  • IE8 - всегда указывать, что вкладка/окно в настоящее время активна (.now() всегда возвращает true для меня)

Ответ 5

Я создаю чат Comet для своего приложения, и когда я получаю сообщение от другого пользователя, я использую:

if(new_message){
    if(!document.hasFocus()){
        audio.play();
        document.title="Have new messages";
    }
    else{
        audio.stop();
        document.title="Application Name";
    } 
}

Ответ 7

Я начал с использования вики-ответа сообщества, но понял, что он не обнаруживает события alt-tab в Chrome. Это потому, что он использует первый доступный источник событий, и в этом случае это API видимости страницы, который в Chrome, похоже, не отслеживает alt-tabbing.

Я решил немного изменить скрипт, чтобы отслеживать все возможные события для изменения фокуса страницы. Здесь вы можете добавить функцию:

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) {
        document.addEventListener('visibilitychange',
            function() {(document.hidden ? unfocused : focused)()});
    }
    if ('mozHidden' in document) {
        document.addEventListener('mozvisibilitychange',
            function() {(document.mozHidden ? unfocused : focused)()});
    }
    if ('webkitHidden' in document) {
        document.addEventListener('webkitvisibilitychange',
            function() {(document.webkitHidden ? unfocused : focused)()});
    }
    if ('msHidden' in document) {
        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;
};

Используйте это так:

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

Эта версия прослушивает все различные события видимости и запускает обратный вызов, если любое из них вызывает изменение. focused и unfocused обработчики гарантируют, что обратный вызов не будет вызываться несколько раз, если несколько API поймают одно и то же изменение видимости

Ответ 8

Это действительно сложно. Кажется, что нет решения, учитывая следующие требования.

  • На странице представлены iframe, над которыми вы не контролируете
  • Вы хотите отслеживать изменение состояния видимости независимо от изменения, вызванного изменением TAB (ctrl + tab) или сменой окна (alt + tab)

Это происходит потому, что:

  • На странице API видимости вы можете с уверенностью сказать вам об изменении вкладок (даже с iframe), но он не может сказать вам, когда пользователь меняет окна.
  • Прослушивание событий размытия/фокуса окна может обнаруживать вкладки alt + tabs и ctrl +, если iframe не имеет фокуса.

Учитывая эти ограничения, можно реализовать решение, которое объединяет - API Видимости страницы - размытие/фокус окна - document.activeElement

Это способ:

  • 1) ctrl + tab, когда главная страница имеет фокус: YES
  • 2) ctrl + tab, если iframe имеет фокус: YES
  • 3) alt + tab, когда главная страница имеет фокус: ДА
  • 4) alt + tab, если iframe имеет фокус: NO < - bummer

Когда фокус iframe имеет фокус, ваши события размытия/фокуса вообще не вызываются, а API видимости страницы не запускается на вкладке alt +.

Я построил решение @AndyE и реализовал это (почти хорошее) решение здесь: https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html (извините, у меня были некоторые проблемы с JSFiddle).

Это также доступно в Github: https://github.com/qmagico/estante-components

Это работает на хром/хром. Он работает на firefox, за исключением того, что он не загружает содержимое iframe (любая идея почему?)

В любом случае, чтобы решить последнюю проблему (4), единственный способ сделать это - прослушивать события размытия/фокусировки на iframe. Если у вас есть некоторый контроль над iframes, вы можете использовать API PostMessage для этого.

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

Я до сих пор не тестировал это с достаточным количеством браузеров. Если вы можете найти больше информации о том, где это не работает, сообщите мне, пожалуйста, в комментариях ниже.

Ответ 9

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/

Ответ 10

это работает для меня на Chrome 67, Firefox 67,

if(!document.hasFocus()) {
    // do stuff
}

Ответ 12

u может использовать:

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

})();

Ответ 13

Это адаптация ответа от Энди Е.

Это выполнит задачу, например. обновлять страницу каждые 30 секунд, но только если страница видна и сфокусирована.

Если видимость не может быть обнаружена, будет использоваться только фокус.

Если пользователь обращает внимание на страницу, она немедленно обновится

Страница не будет обновляться до 30 секунд после любого вызова ajax

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

Ответ 14

Несколько более сложным способом было бы использовать setInterval() для проверки положения мыши и сравнения с последней проверкой. Если мышь не переместилась за определенное количество времени, пользователь, вероятно, простаивает.

У этого есть дополнительное преимущество, говоря, что пользователь простаивает, вместо того, чтобы просто проверять, не работает ли окно.

Как отмечают многие люди, это не всегда хороший способ проверить, не работает ли окно пользователя или браузера, поскольку пользователь может даже не использовать мышь или смотреть видео или подобное. Я просто предлагаю один из возможных способов проверить простоя.

Ответ 15

Для решения без jQuery проверьте Visibility.js, в котором содержится информация о трех состояниях страницы

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

а также удобные обертки для 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();
});

Также доступен резерв для старых браузеров (IE < 10; iOS < 7)

Ответ 16

Для angular.js, вот директива (на основе принятого ответа), которая позволит вашему контроллеру реагировать на изменение видимости:

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

});

Вы можете использовать его, как в этом примере: <div react-on-window-focus="refresh()">, где refresh() - это функция области видимости в пределах любого контроллера, находящегося в области видимости.

Ответ 17

Если вы хотите выполнить в целом размытие браузера: Как я уже сказал, если браузер не будет сосредоточен, ни один из предложенных событий не загорится. Моя идея - подсчитать в цикле и reset счетчик, если огонь события. Если счетчик достигает предела, я делаю location.href на другую страницу. Это также срабатывает, если вы работаете с dev-инструментами.

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

Это проект успешно протестирован в FF.

Ответ 18

Просто хотел добавить: Вопрос не совсем ясен. "Когда пользователь не смотрит на сайт (т.е. Окно или вкладка не имеют фокуса)..."

Я могу посмотреть сайт, если он не имеет фокуса. Большинство настольных систем могут показывать окна параллельно:)

Вот почему API видимости страницы, вероятно, правильный ответ, потому что он предотвращает обновление сайта, когда "пользователь не может видеть обновления", который может сильно отличаться от "вкладки, не имеет фокуса".