Existe uma maneira de detectar se uma janela do navegador não está ativa no momento?

585

Eu tenho JavaScript que realiza atividades periodicamente. Quando o usuário não está vendo o site (por exemplo, a janela ou a guia não tem foco), seria bom não executar.

Existe uma maneira de fazer isso usando JavaScript?

Meu ponto de referência: o Bate-papo do Gmail emite um som se a janela que você está usando não estiver ativa.

Luke Francl
fonte
8
Para quem não está satisfeito com as respostas abaixo, confira a requestAnimationFrameAPI ou use o recurso moderno de que a frequência de setTimeout/ setIntervalé reduzida quando a janela não está visível (1 segundo no Chrome, por exemplo).
10243 Robbie W
2
document.body.onblur = function (e) {console.log ('lama');} trabalhou para elementos não focados.
WhyMe
2
Veja esta resposta para uma solução compatível com vários navegadores que usa a API de visibilidade de página do W3C, voltando para blur/ focusem navegadores que não a suportam.
Mathias Bynens
2
80% das respostas abaixo não são respostas para esta pergunta . A pergunta não está ativa no momento, mas muitas respostas abaixo não são visíveis, o que não é uma resposta para esta pergunta. Eles devem sem dúvida ser marcada como "não é uma resposta"
gman

Respostas:

691

Desde que escrevemos originalmente esta resposta, uma nova especificação alcançou o status de recomendação graças ao W3C. A API de visibilidade da página (no MDN ) agora nos permite detectar com mais precisão quando uma página está oculta para o usuário.

document.addEventListener("visibilitychange", onchange);

Suporte atual do navegador:

  • Chrome 13 ou superior
  • Internet Explorer 10 ou superior
  • Firefox 10 ou superior
  • Opera 12.10+ [ ler notas ]

O código a seguir volta ao método menos desfocado / foco menos confiável em navegadores incompatíveis:

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

onfocusine onfocusoutsão necessários para o IE 9 e inferior , enquanto todos os outros fazem uso de onfocuse onblur, exceto para iOS, que usa onpageshowe onpagehide.

Andy E
fonte
1
@bellpeace: o IE deve se propagar focusine focusoutdo iframe para a janela superior. Para navegadores mais recentes, você precisaria lidar com os eventos focuse blurem cada windowobjeto do iframe . Você deve usar o código atualizado que acabei de adicionar, que abrangerá pelo menos esses casos em navegadores mais recentes.
Andy E
3
@JulienKronegg: é por isso que minha resposta menciona especificamente a API de visibilidade da página que entrou no status de rascunho de trabalho depois que originalmente escrevi minha resposta. Os métodos de foco / desfoque fornecem funcionalidade limitada para navegadores mais antigos. Vincular-se a outros eventos, como na sua resposta, não cobre muito mais do que isso e corre mais risco de diferenças de comportamento (como o IE não dispara o mouseout quando uma janela aparece abaixo do cursor). Sugiro que uma ação mais apropriada seja exibir uma mensagem ou ícone indicando ao usuário que as atualizações podem ser menos frequentes devido à inatividade da página.
Andy E
6
@ AndyEu tentei esta solução em cromo. Funciona se eu alterar as guias, mas não se alterar as janelas (ALT + guia). Deveria? Aqui está um violino - jsfiddle.net/8a9N6/17
Tony Lâmpada
2
@ Heliodor: eu gostaria de manter o código na resposta mínima por enquanto. Ele nunca foi planejado para ser uma solução completa de recortar e colar, pois os implementadores podem querer evitar definir uma classe no corpo e executar uma ação completamente diferente (como parar e iniciar um cronômetro).
Andy E
8
@AndyE Sua solução parece funcionar apenas se o usuário alterar as guias ou minimizar / maximizar a janela. No entanto, o evento onchange não é acionado se o usuário deixar a guia ativa, mas maximiza outro programa sobre ele na barra de tarefas. Existe uma solução para esse cenário? Obrigado!
precisa saber é o seguinte
132

Eu usaria o jQuery porque tudo o que você precisa fazer é o seguinte:

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

Ou pelo menos funcionou para mim.

Carson Wright
fonte
1
para mim esta chamada duas vezes em iframe
msangel
No Firefox, se você clicar no console do firebug (na mesma página), o windowfoco será perdido, o que é correto, mas dependendo de qual seja sua intenção, talvez não seja o que você precisa.
Majid Fouladpour
21
Isto já não funciona para as versões atuais de navegadores modernos, ver a resposta aprovada (Página Visibilidade API)
Jon z
Esta solução não funciona no iPad utilize "pageshow" evento
ElizaS
O BLUR e o FOCUS são disparados quando a página é carregada. Quando eu abrir uma nova janela de minha página não acontece nada, mas uma vez que a nova janela fecha ambos os incêndios evento fora: / (usando IE8)
SearchForKnowledge
49

Existem três métodos típicos usados ​​para determinar se o usuário pode ver a página HTML, mas nenhum deles funciona perfeitamente:

  • A API do W3C Page Visibility deve fazer isso (suportada desde: Firefox 10, MSIE 10, Chrome 13). No entanto, essa API somente gera eventos quando a guia do navegador é totalmente substituída (por exemplo, quando o usuário muda de uma guia para outra). A API não gera eventos quando a visibilidade não pode ser determinada com 100% de precisão (por exemplo, Alt + Tab para alternar para outro aplicativo).

  • O uso de métodos baseados em foco / desfoque fornece muitos falsos positivos. Por exemplo, se o usuário exibir uma janela menor na parte superior da janela do navegador, ela perderá o foco ( onbluraumentado), mas o usuário ainda poderá vê-lo (portanto, ainda precisará ser atualizado). Veja também http://javascript.info/tutorial/focus

  • Confiar na atividade do usuário (movimento do mouse, cliques, tecla digitada) também fornece muitos falsos positivos. Pense no mesmo caso acima, ou em um usuário assistindo a um vídeo.

Para melhorar os comportamentos imperfeitos descritos acima, uso uma combinação dos 3 métodos: API de visibilidade do W3C, em seguida, foco / desfoque e métodos de atividade do usuário para reduzir a taxa de falsos positivos. Isso permite gerenciar os seguintes eventos:

  • Alterando a guia do navegador para outra (100% de precisão, graças à API de visibilidade da página do W3C)
  • Página potencialmente oculta por outra janela, por exemplo, devido a Alt + Tab (probabilística = não 100% precisa)
  • Atenção do usuário potencialmente não focada na página HTML (probabilística = não 100% precisa)

É assim que funciona: quando o documento perde o foco, a atividade do usuário (como a movimentação do mouse) no documento é monitorada para determinar se a janela está visível ou não. A probabilidade de visibilidade da página é inversamente proporcional à hora da última atividade do usuário na página: se o usuário não faz atividade no documento por um longo tempo, é provável que a página não esteja visível. O código abaixo imita a API de visibilidade da página W3C: ela se comporta da mesma maneira, mas possui uma pequena taxa de falsos positivos. Tem a vantagem de ser multibrowser (testado no Firefox 5, Firefox 10, MSIE 9, MSIE 7, Safari 5, Chrome 9).

    <div id = "x"> </div>

    <script>
    / **
    Registra o manipulador no evento para o objeto especificado.
    @param obj o objeto que irá gerar o evento
    @param evType o tipo de evento: clique, tecla pressionada, mouseover, ...
    @param fn a função de manipulador de eventos
    @param isCapturing define o modo de evento (true = evento de captura, false = evento de bolhas)
    @return true se o manipulador de eventos foi anexado corretamente
    * /
    função addEvent (obj, evType, fn, isCapturing) {
      if (isCapturing == null) isCapturing = false; 
      if (obj.addEventListener) {
        // Raposa de fogo
        obj.addEventListener (evType, fn, isCapturing);
        return true;
      } se if (obj.attachEvent) {
        // MSIE
        var r = obj.attachEvent ('on' + evType, fn);
        retornar r;
      } outro {
        retorna falso;
      }
    }

    // registre-se na possível alteração na visibilidade da página
    addEvent (documento, "mudança de visibilidade potencial", função (evento) {
      document.getElementById ("x"). innerHTML + = "potencialVisilityChange: potencialHidden =" + document.potentialHidden + ", document.potentiallyHiddenSince =" + document.potentiallyHiddenSince + "s <br>";
    });

    // registre-se na API de visibilidade da página W3C
    var oculto = nulo;
    var visibleChange = null;
    if (typeof document.mozHidden! == "undefined") {
      oculto = "mozHidden";
      visibleChange = "mozvisibilitychange";
    } else if (typeof document.msHidden! == "undefined") {
      oculto = "msHidden";
      visibleChange = "msvisibilitychange";
    } else if (typeof document.webkitHidden! == "undefined") {
      hidden = "webkitHidden";
      visibleChange = "webkitvisibilitychange";
    } else if (typeof document.hidden! == "hidden") {
      oculto = "oculto";
      visibleChange = "visiblechange";
    }
    if (oculto! = nulo && visibleChange! = nulo) {
      addEvent (documento, visibleChange, função (evento) {
        document.getElementById ("x"). innerHTML + = visibleChange + ":" + hidden + "=" + documento [oculto] + "<br>";
      });
    }


    var potencialPageVisibility = {
      pageVisibilityChangeThreshold: 3 * 3600, // em segundos
      init: function () {
        função setAsNotHidden () {
          var dispatchEventRequired = document.potentialHidden;
          document.potentialHidden = false;
          document.potentiallyHiddenSince = 0;
          if (dispatchEventRequired) dispatchPageVisibilityChangeEvent ();
        }

        função initPotentiallyHiddenDetection () {
          if (! hasFocusLocal) {
            // a janela não tem o foco => verifica a atividade do usuário na janela
            lastActionDate = new Date ();
            if (timeoutHandler! = null) {
              clearTimeout (timeoutHandler);
            }
            timeoutHandler = setTimeout (checkPageVisibility, potencialPageVisibility.pageVisibilityChangeThreshold * 1000 + 100); // +100 ms para evitar problemas de arredondamento no Firefox
          }
        }

        função dispatchPageVisibilityChangeEvent () {
          unifiedVisilityChangeEventDispatchAllowed = false;
          var evt = document.createEvent ("Evento");
          evt.initEvent ("mudança de visibilidade potencial", verdadeira, verdadeira);
          document.dispatchEvent (evt);
        }

        função checkPageVisibility () {
          var potencialHiddenDuration = (hasFocusLocal || lastActionDate == null? 0: Math.floor ((new Date (). getTime () - lastActionDate.getTime ()) / 1000));
                                        document.potentiallyHiddenSince = potencialHiddenDuration;
          if (potencialHiddenDuration> = potencialPageVisibility.pageVisibilityChangeThreshold &&! document.potentialHidden) {
            // limiar de alteração da visibilidade da página raiched => aumenta o nível
            document.potentialHidden = true;
            dispatchPageVisibilityChangeEvent ();
          }
        }

        var lastActionDate = nulo;
        var hasFocusLocal = true;
        var hasMouseOver = true;
        document.potentialHidden = false;
        document.potentiallyHiddenSince = 0;
        var timeoutHandler = nulo;

        addEvent (documento, "pageshow", função (evento) {
          document.getElementById ("x"). innerHTML + = "pageshow / doc: <br>";
        });
        addEvent (documento, "pagehide", função (evento) {
          document.getElementById ("x"). innerHTML + = "ocultar página / doc: <br>";
        });
        addEvent (janela, "pageshow", função (evento) {
          document.getElementById ("x"). innerHTML + = "pageshow / win: <br>"; // gerado quando a página é exibida pela primeira vez
        });
        addEvent (janela, "ocultar página", função (evento) {
          document.getElementById ("x"). innerHTML + = "pagehide / win: <br>"; // não gerado
        });
        addEvent (documento, "mousemove", função (evento) {
          lastActionDate = new Date ();
        });
        addEvent (documento, "mouseover", função (evento) {
          hasMouseOver = true;
          setAsNotHidden ();
        });
        addEvent (documento, "mouseout", função (evento) {
          hasMouseOver = false;
          initPotentiallyHiddenDetection ();
        });
        addEvent (janela, "desfoque", função (evento) {
          hasFocusLocal = false;
          initPotentiallyHiddenDetection ();
        });
        addEvent (janela, "foco", função (evento) {
          hasFocusLocal = true;
          setAsNotHidden ();
        });
        setAsNotHidden ();
      }
    }

    potencialPageVisibility.pageVisibilityChangeThreshold = 4; // 4 segundos para teste
    potencialPageVisibility.init ();
    </script>

Como atualmente não existe uma solução que funcione em vários navegadores sem falso positivo, é melhor pensar duas vezes em desativar a atividade periódica em seu site.

Julien Kronegg
fonte
Usar um operador de comparação estrita na string 'indefinido' em vez da palavra-chave indefinida causaria falsos positivos no código acima?
21416 Jonathon
@kiran: Na verdade, ele está trabalhando com Alt + Tab. Você não pode determinar se a página está oculta quando você faz uma tecla Alt + Tab, pois pode mudar para uma janela menor para não garantir que sua página esteja totalmente oculta. É por isso que uso a noção de "potencialmente oculto" (no exemplo, o limite é definido para 4 segundos, portanto, você precisa mudar para outra janela usando Alt + Tab por pelo menos 4 segundos). No entanto, seu comentário mostra que a resposta não era tão clara, então eu a reformulei.
Julien Kronegg
@JulienKronegg Acho que essa é a melhor solução ainda. No entanto, o código acima precisa extremamente de refatoração e abstrações. Por que você não faz o upload para o GitHub e deixa a comunidade refatorá-lo?
Jacob
1
@ Jacob Estou feliz que você tenha gostado da minha solução. Sinta-se livre para promovê-lo em um projeto do GitHub por conta própria. Dou o código com licença Creative Commons BY creativecommons.org/licenses/by/4.0
Julien Kronegg 20/17/17 /
26

Há uma biblioteca interessante disponível no GitHub:

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

Exemplo:

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

Testei a versão 1.0.1 em todos os navegadores que tenho e posso confirmar que funciona com:

  • IE9, IE10
  • FF 26.0
  • Chrome 34.0

... e provavelmente todas as versões mais recentes.

Não funciona totalmente com:

  • IE8 - sempre indica que a guia / janela está ativa no momento ( .now()sempre retorna truepara mim)
omnomnom
fonte
A resposta aceita causou problemas no IE9. Esta biblioteca funciona muito bem.
Tom Temã
20

Usando: API de visibilidade da página

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

Eu posso usar ? http://caniuse.com/#feat=pagevisibility

l2aelba
fonte
A questão não é sobre a visibilidade da página. É sobre não ativo / ativo
gman
Acho OP não está falando sobre a função do IDE
l2aelba
1
Também não estou falando de ide's. Estou falando de alt-tabbing / cmd-tabbing para outro aplicativo. De repente, a página não está ativa. A API de visibilidade da página não me ajuda a saber se a página não está ativa, apenas ajuda a saber se a possivelmente não está visível.
gman
18

Crio um Comet Chat para o meu aplicativo e, quando recebo uma mensagem de outro usuário, uso:

if(new_message){
    if(!document.hasFocus()){
        audio.play();
        document.title="Have new messages";
    }
    else{
        audio.stop();
        document.title="Application Name";
    } 
}
infinito84
fonte
2
A solução mais limpa, com o apoio de volta para IE6
Paul Cooper
4
document.hasFocus()é a maneira mais limpa de fazer isso. Todas as outras maneiras de usar a API de visibilidade ou evento baseado em ou procurar vários níveis de atividade do usuário / falta de atividade tornam-se complicadas demais e cheias de casos e falhas de borda. coloque-o em um intervalo simples e crie um evento personalizado quando os resultados forem alterados. Exemplo: jsfiddle.net/59utucz6/1
danatcofo 29/03
1
Eficiente e, diferentemente das outras soluções, fornece feedback correto quando você alterna para outra guia ou janela do navegador e até um aplicativo diferente.
Ow3n 17/07/19
Sem dúvida, a sua forma mais limpa, mas ele não funciona no Firefox
hardik Chugh
1
Se eu abrir as ferramentas do desenvolvedor do Chrome, document.hasFocus () será igual a false. Ou mesmo se você clicar no painel superior do navegador, o mesmo acontece. Não tenho certeza se esta solução é adequada para pausar vídeo, animação etc.
tylik
16

Comecei a usar a resposta do wiki da comunidade, mas percebi que não estava detectando eventos com a tecla alt-tab no Chrome. Isso ocorre porque ele usa a primeira fonte de eventos disponível e, nesse caso, é a API de visibilidade da página, que no Chrome parece não rastrear as abas alternadas.

Decidi modificar um pouco o script para acompanhar todos os eventos possíveis para alterações no foco da página. Aqui está uma função que você pode incluir:

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

Use-o assim:

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

Esta versão escuta todos os diferentes eventos de visibilidade e aciona um retorno de chamada se algum deles causar uma alteração. Os manipuladores focusede unfocusedgarantem que o retorno de chamada não seja chamado várias vezes se várias APIs capturarem a mesma alteração de visibilidade.

Daniel Buckmaster
fonte
O Chrome, por exemplo, possui ambos document.hiddene document.webkitHidden. Sem o elsena ifconstrução obteríamos 2 chamadas de retorno de chamada certo?
Christiaan Westerbeek
@ChristiaanWesterbeek Esse é um bom ponto, eu não pensei nisso! Se você pode editar este post ir em frente e eu vou aceitar :)
Daniel Buckmaster
Ei, espere um minuto: a edição para adicionar "outros" sugeridos por ChristiaanWesterbeek e realmente adicionados por 1.21Gigawatts não parece uma boa idéia: ela derrota a compra original da idéia de Daniel, que é tentar todos os métodos em paralelo. E não há risco de o retorno de chamada ser chamado duas vezes porque o foco () e o foco () suprimem as chamadas extras quando nada está mudando. Realmente parece que deveríamos reverter para a primeira rev.
Louis Semprini
@LouisSemprini é uma ótima captura. Eu tinha esquecido a intenção original do código! Eu restaurei o original e adicionei uma explicação!
Daniel Buckmaster
verificando isso a partir de hoje, ele não detecta alt + tab pelo menos no Chrome 78 + macos #
Hugo Gresse
7

Isso é realmente complicado. Parece não haver solução, considerando os seguintes requisitos.

  • A página inclui iframes sobre os quais você não tem controle
  • Você deseja acompanhar a alteração do estado de visibilidade, independentemente da alteração ser acionada por uma alteração da TAB (ctrl + tab) ou por uma janela (alt + tab)

Isso acontece porque:

  • A página Visibility API pode informar com segurança sobre uma alteração de guia (mesmo com iframes), mas não pode informar quando o usuário altera as janelas.
  • Ouvir eventos de desfoque / foco da janela pode detectar as teclas alt + tabs e ctrl + tabs, desde que o iframe não tenha foco.

Dadas essas restrições, é possível implementar uma solução que combine - A página API de visibilidade - desfoque / foco da janela - document.activeElement

Que é capaz de:

  • 1) ctrl + tab quando a página pai estiver focada: SIM
  • 2) ctrl + tab quando o iframe estiver focado: SIM
  • 3) alt + tab quando a página principal tiver foco: SIM
  • 4) alt + tab quando o iframe estiver focado: NÃO <- chatice

Quando o iframe tem foco, seus eventos de desfoque / foco não são invocados e a API de visibilidade da página não é acionada na guia alt +.

Desenvolvi a solução da @ AndyE e implementei esta (quase boa) solução aqui: https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html (desculpe, tive alguns problemas com o JSFiddle).

Isso também está disponível no Github: https://github.com/qmagico/estante-components

Isso funciona em cromo / cromo. Ele funciona no firefox, exceto pelo fato de não carregar o conteúdo do iframe (alguma ideia por que?)

De qualquer forma, para resolver o último problema (4), a única maneira de fazer isso é ouvir eventos de desfoque / foco no iframe. Se você tiver algum controle sobre os iframes, poderá usar a API postMessage para fazer isso.

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

Ainda não testei isso com navegadores suficientes. Se você puder encontrar mais informações sobre onde isso não funciona, entre em contato nos comentários abaixo.

Tony Lâmpada
fonte
Nos meus testes, também funcionou no IE9, IE10 e Chrome no Android.
Tony Lâmpada 17/09/2013
1
Parece que o IPAD precisa de uma solução completamente diferente - stackoverflow.com/questions/4940657/…
Tony Lâmpada
3
Todos esses links são 404 :(
Daniel Buckmaster
6
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
fonte
5

isso funciona para mim no chrome 67, firefox 67,

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

você pode usar:

(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
fonte
3

No HTML 5, você também pode usar:

  • onpageshow: Script a ser executado quando a janela se tornar visível
  • onpagehide: Script a ser executado quando a janela estiver oculta

Vejo:

assaltos
fonte
2
Eu acho que isso está relacionado ao BFCache: quando o usuário clica em Voltar ou Avançar - não está relacionado à página estar na parte superior da área de trabalho do computador.
Nonopolarity
2

Esta é uma adaptação da resposta de Andy E.

Isso fará uma tarefa, por exemplo, atualizar a página a cada 30 segundos, mas apenas se a página estiver visível e focada.

Se a visibilidade não puder ser detectada, apenas o foco será usado.

Se o usuário focar a página, ele será atualizado imediatamente

A página não será atualizada novamente até 30 segundos após qualquer chamada 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);
}
Roger
fonte
Baseando-se em foco / borrão métodos não funcionam (que lhe dá um monte de falso positivo), consulte stackoverflow.com/a/9502074/698168
Julien Kronegg
2

Para uma solução sem jQuery, acesse Visibility.js, que fornece informações sobre três estados de página

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

e também wrappers de conveniência para 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();
});

Também está disponível um fallback para navegadores mais antigos (IE <10; iOS <7)

Niko
fonte
e o suporte ao navegador? por enquanto bifurcando-se bem no chrome, safari e firefox.
Selva Ganapathi 24/03
1

Uma maneira um pouco mais complicada seria usar setInterval()para verificar a posição do mouse e comparar com a última verificação. Se o mouse não se mover em um período de tempo definido, o usuário provavelmente está ocioso.

Isso tem a vantagem adicional de dizer se o usuário está ocioso, em vez de apenas verificar se a janela não está ativa.

Como muitas pessoas apontaram, essa nem sempre é uma boa maneira de verificar se a janela do usuário ou do navegador está ociosa, pois o usuário pode nem estar usando o mouse ou assistindo a um vídeo ou semelhante. Estou apenas sugerindo uma maneira possível de verificar a ociosidade.

Austin Hyde
fonte
30
A menos que o usuário não tenha um mouse.
user1686
@Annan: É codinghorror.com/blog/2007/03/… agora.
chiborg
Isso também não joga dados se o usuário estiver assistindo a um vídeo
jamiew
você pode usar onkeypress ou outros eventos semelhantes para redefinir o timer e resolver o problema que não é do mouse. Claro que ainda não trabalho para usuários que estão buscando ativamente na página para assistir a um vídeo, estudar uma imagem, etc.
joshuahedlund
1

Para angular.js, aqui está uma diretiva (baseada na resposta aceita) que permitirá que seu controlador reaja a uma alteração na visibilidade:

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

});

Você pode usá-lo como este exemplo:, <div react-on-window-focus="refresh()">Onde refresh()está uma função de escopo no escopo de qualquer Controller que esteja no escopo.

Steve Campbell
fonte
0

Aqui está uma solução sólida e moderna. (Curto um doce 👌🏽)

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

Isso configurará um ouvinte para disparar quando qualquer evento de visibilidade for disparado, o que poderia ser um foco ou um borrão.

Cory Robinson
fonte
0

Se você deseja atuar em todo o borrão do navegador : Como comentei, se o navegador perder o foco, nenhum dos eventos sugeridos será acionado. Minha idéia é contar em um loop e redefinir o contador se um evento disparar. Se o contador atingir um limite, eu faço um location.href para outra página. Isso também dispara se você trabalha com 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();

Este é um rascunho testado com sucesso no FF.

BF
fonte