Есть ли способ обнаружить, если окно браузера не активно в данный момент? - PullRequest
538 голосов
/ 29 июня 2009

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

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

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

Ответы [ 18 ]

639 голосов
/ 29 июня 2009

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

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

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

(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.

124 голосов
/ 09 ноября 2011

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

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

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

44 голосов
/ 29 февраля 2012

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

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

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

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

Чтобы улучшить несовершенное поведение, описанное выше, я использую комбинацию из 3 методов: 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>

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

23 голосов
/ 16 апреля 2014

На 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 для меня)
14 голосов
/ 05 сентября 2016

Использование: API видимости страницы

document.addEventListener( 'visibilitychange' , function() {
    if (document.hidden) {
        console.log('bye');
    } else {
        console.log('well back');
    }
}, false );

Могу ли я использовать? http://caniuse.com/#feat=pagevisibility

14 голосов
/ 08 сентября 2013

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

if(new_message){
    if(!document.hasFocus()){
        audio.play();
        document.title="Have new messages";
    }
    else{
        audio.stop();
        document.title="Application Name";
    } 
}
10 голосов
/ 02 августа 2016

Я начал с использования вики-ответа сообщества, но понял, что он не обнаруживает события 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-интерфейсов фиксируют одно и то же изменение видимости.

7 голосов
/ 17 сентября 2013

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

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

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

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

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

Это умеет:

  • 1) Ctrl + Tab, когда родительская страница имеет фокус: ДА
  • 2) Ctrl + Tab, если iframe имеет фокус: ДА
  • 3) alt + tab, когда родительская страница имеет фокус: ДА
  • 4) alt + tab, когда фрейм имеет фокус: NO <- облом </li>

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

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

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

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

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

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

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

4 голосов
/ 06 апреля 2013
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/

3 голосов
/ 18 августа 2018

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

if(!document.hasFocus()) {
    // do stuff
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...