web-dev-qa-db-fra.com

Existe-t-il un moyen de détecter si une fenêtre du navigateur n'est pas active?

J'ai JavaScript qui fait de l'activité périodiquement. Lorsque l'utilisateur ne regarde pas le site (c'est-à-dire que la fenêtre ou l'onglet n'a pas le focus), il serait agréable de ne pas s'exécuter.

Y a-t-il un moyen de faire cela en utilisant JavaScript?

Mon point de référence: Gmail Chat émet un son si la fenêtre que vous utilisez n'est pas active.

547
Luke Francl

Depuis que cette réponse a été écrite, une nouvelle spécification a atteint le statut recommandé grâce au W3C. La API de visibilité de la page (sur MDN ) nous permet maintenant de détecter plus précisément le moment où une page est masquée par l'utilisateur.

document.addEventListener("visibilitychange", onchange);

Support actuel du navigateur:

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

Le code suivant revient à la méthode moins fiable de flou/focus dans les navigateurs incompatibles:

(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 et onfocusout sont requis pour IE 9 et inférieur , tandis que tous les autres utilisent onfocus et onblur, sauf pour iOS, qui utilise onpageshow et onpagehide.

650
Andy E

J'utiliserais jQuery car tout ce que vous avez à faire est la suivante:

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

Ou du moins cela a fonctionné pour moi.

124
Carson Wright

Une librairie soignée est disponible sur GitHub:

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

Exemple:

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

J'ai testé la version 1.0.1 sur tous les navigateurs que j'ai et peux confirmer qu'il fonctionne avec:

  • IE9, IE10
  • 26.0 FF
  • Chrome 34.0

... et probablement toutes les nouvelles versions.

Ne fonctionne pas complètement avec:

  • IE8 - indique toujours que l'onglet/la fenêtre est actuellement actif (.now() renvoie toujours true pour moi)
23
omnomnom

Je crée un chat Comet pour mon application et, lorsque je reçois un message d'un autre utilisateur, j'utilise:

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

Utilisation de: API de visibilité de la page

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

Puis-je utiliser? http://caniuse.com/#feat=pagevisibility

15
l2aelba

J'ai commencé par utiliser la réponse du wiki de la communauté, mais je me suis rendu compte qu'il ne détectait pas les événements d'alt-tab dans Chrome. En effet, il utilise la première source d'événements disponible et, dans ce cas, l'API de visibilité de la page, qui dans Chrome ne semble pas suivre le suivi des alt-tabs.

J'ai décidé de modifier un peu le script pour garder une trace de tous événements possibles pour les changements de focus de page. Voici une fonction que vous pouvez visiter:

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

Utilisez-le comme ceci:

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

Cette version écoute tous les différents événements de visibilité et déclenche un rappel si l'un d'entre eux provoque une modification. Les gestionnaires focused et unfocused s'assurent que le rappel n'est pas appelé plusieurs fois si plusieurs API capturent le même changement de visibilité.

11
Daniel Buckmaster

C'est vraiment délicat. Il semble n'y avoir aucune solution compte tenu des exigences suivantes.

  • La page contient des iframes sur lesquels vous n’avez aucun contrôle.
  • Vous souhaitez suivre l'évolution de l'état de visibilité, que celle-ci soit déclenchée par une modification de tabulation (Ctrl + Tab) ou par une fenêtre (Alt + Tab).

Cela se produit parce que:

  • L'API de visibilité de la page peut vous indiquer de manière fiable un changement d'onglet (même avec des iframes), mais elle ne peut pas vous dire quand l'utilisateur change de fenêtre.
  • L'écoute d'événements de flou/focus de fenêtre peut détecter alt + tabs et ctrl + tabs, tant que l'iframe n'a pas le focus.

Compte tenu de ces restrictions, il est possible de mettre en œuvre une solution combinant: - l'API de visibilité de la page - flou/focus de fenêtre - document.activeElement

Qui est capable de:

  • 1) Ctrl + Tab quand la page parent a le focus: OUI
  • 2) ctrl + tab quand iframe a le focus: OUI
  • 3) alt + tab quand la page parent a le focus: OUI
  • 4) alt + tab quand l'iframe a le focus: NON <- bummer

Lorsque l'iframe a le focus, vos événements de flou/focus ne sont pas du tout invoqués et l'API de visibilité de la page ne se déclenche pas sur alt + tab.

Je me suis inspiré de la solution de @ AndyE pour mettre en œuvre cette (presque bonne) solution ici: https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html (désolé, certains problème avec JSFiddle).

Ceci est également disponible sur Github: https://github.com/qmagico/estante-components

Cela fonctionne sur le chrome/chrome. Cela fonctionne bien sur firefox, sauf qu'il ne charge pas le contenu de l'iframe (pourquoi?)

Quoi qu'il en soit, pour résoudre le dernier problème (4), la seule façon de le faire est d'écouter les événements de flou/focus sur l'iframe. Si vous avez un certain contrôle sur les iframes, vous pouvez utiliser l'API postMessage pour le faire.

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

Je n'ai toujours pas testé cela avec suffisamment de navigateurs. Si vous pouvez trouver plus d'informations sur les endroits où cela ne fonctionne pas, merci de me le faire savoir dans les commentaires ci-dessous.

7
Tony Lâmpada
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/

5
yckart

cela fonctionne pour moi sur chrome 67, firefox 67,

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

En HTML 5, vous pouvez également utiliser:

  • onpageshow: Script à exécuter lorsque la fenêtre devient visible
  • onpagehide: Script à exécuter lorsque la fenêtre est masquée

Voir:

3
roberkules

vous pouvez utiliser:

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

})();
3
maryam

Ceci est une adaptation de la réponse de Andy E.

Cela fera une tâche par exemple. actualisez la page toutes les 30 secondes, mais uniquement si la page est visible et centrée.

Si la visibilité ne peut pas être détectée, seul le focus sera utilisé.

Si l'utilisateur se concentre sur la page, alors il mettra à jour immédiatement

La page ne sera mise à jour que 30 secondes après un appel 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);
}
2
roger

Pour angular.js, voici une directive (basée sur la réponse acceptée) qui permettra à votre contrôleur de réagir à un changement de visibilité:

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

});

Vous pouvez l'utiliser comme dans cet exemple: <div react-on-window-focus="refresh()">, où refresh() est une fonction de portée dans la portée de tout contrôleur situé dans la portée.

1
Steve Campbell

Pour une solution sans jQuery, consultez Visibility.js qui fournit des informations sur les trois états de page.

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

et aussi commodité-wrappers pour 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();
});

Une solution de secours pour les anciens navigateurs (IE <10; iOS <7) est également disponible.

1
Niko

Une méthode légèrement plus compliquée consisterait à utiliser setInterval() pour vérifier la position de la souris et la comparer à la dernière vérification. Si la souris ne s'est pas déplacée dans le temps imparti, l'utilisateur est probablement inactif.

Cela a l’avantage supplémentaire de dire si l’utilisateur est inactif, au lieu de seulement en vérifiant si la fenêtre n’est pas active.

Comme de nombreuses personnes l’ont fait remarquer, ce n’est pas toujours un bon moyen de vérifier si la fenêtre de l’utilisateur ou du navigateur est inactive, étant donné que l’utilisateur peut même ne pas utiliser la souris ou regarder une vidéo ou similaire. Je ne fais que suggérer un moyen possible de vérifier le temps mort.

1
Austin Hyde

Je voulais juste ajouter: La question n’est pas claire. "Lorsque l'utilisateur ne regarde pas le site (c'est-à-dire que la fenêtre ou l'onglet n'a pas le focus) ..."

Je peux regarder un site quand il n'a pas de focus. La plupart des systèmes de bureau sont capables d'afficher des fenêtres en parallèle :)

C'est pourquoi l'API de visibilité de page est probablement la bonne réponse car elle empêche la mise à jour du site lorsque "l'utilisateur ne peut pas voir les mises à jour", ce qui peut être très différent de "l'onglet n'a pas le focus".

0
HolgerJeromin

Si vous voulez agir sur entier navigateur flo: Comme je l'ai commenté, si le navigateur perd son focus, aucun des événements suggérés ne se déclenche. Mon idée est de compter dans une boucle et de réinitialiser le compteur si un événement se déclenche. Si le compteur atteint une limite, je fais un location.href vers une autre page. Cela se déclenche également si vous travaillez sur 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();

Ceci est un projet testé avec succès sur FF.

0
B.F.