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




vanilla js window width (12)

J'ai JavaScript qui fait 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 de focus), ce serait bien de ne pas fonctionner.

Y a-t-il un moyen de le faire 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.


C'est vraiment difficile. Il ne semble pas y avoir de solution compte tenu des exigences suivantes.

  • La page contient des iframes que vous n'avez aucun contrôle sur
  • Vous souhaitez suivre la modification de l'état de la visibilité, quelle que soit la modification déclenchée par un changement de TAB (Ctrl + Tab) ou un changement de fenêtre (Alt + Tab)

Cela arrive parce que:

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

Compte tenu de ces restrictions, il est possible d'implémenter une solution qui combine - L'API de visibilité de la page - la fenêtre flou / focus - document.activeElement

C'est capable de:

  • 1) ctrl + tab lorsque la page parente a le focus: OUI
  • 2) ctrl + tab lorsque iframe a le focus: OUI
  • 3) alt + tab lorsque la page parente a le focus: OUI
  • 4) alt + tab lorsque iframe a le focus: NO <- bummer

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

J'ai construit sur la solution @ AndyE et mis en œuvre cette solution (presque bonne) ici: https://dl.dropboxusercontent.com/u/2683925/estante-components/visibility_test1.html (désolé, j'ai eu quelques problèmes avec JSFiddle).

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

Cela fonctionne sur chrome / chrome. Il fonctionne sur Firefox, sauf qu'il ne charge pas le contenu iframe (aucune idée 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 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 ce qui ne fonctionne pas, s'il vous plaît laissez-moi savoir dans les commentaires ci-dessous.


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

Cela fera une tâche par exemple actualiser la page toutes les 30 secondes, mais seulement si la page est visible et ciblée.

Si la visibilité ne peut pas être détectée, seule la mise au point sera utilisée.

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

La page ne sera pas mise à jour jusqu'à 30 secondes après tout 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);
}


Il existe 3 méthodes typiques utilisées pour déterminer si l'utilisateur peut voir la page HTML, mais aucune d'elles ne fonctionne parfaitement:

  • L' API W3C Page Visibility est censée le faire (prise en charge depuis: Firefox 10, MSIE 10, Chrome 13). Toutefois, cette API déclenche uniquement des événements lorsque l'onglet du navigateur est entièrement remplacé (par exemple, lorsque l'utilisateur passe d'un onglet à un autre). L'API ne déclenche pas d'événements lorsque la visibilité ne peut pas être déterminée avec une précision de 100% (par exemple Alt + Tab pour passer à une autre application).

  • L'utilisation de méthodes basées sur le focus / flou vous donne beaucoup de faux positifs. Par exemple, si l'utilisateur affiche une fenêtre plus petite en haut de la fenêtre du navigateur, la fenêtre du navigateur perd le focus ( onblur ) mais l'utilisateur est toujours capable de le voir (il doit donc être actualisé). Voir aussi http://javascript.info/tutorial/focus

  • S'appuyer sur l'activité de l'utilisateur (déplacement de la souris, clics, touche typée) vous donne aussi beaucoup de faux positifs. Pensez au même cas que ci-dessus ou à un utilisateur qui regarde une vidéo.

Afin d'améliorer les comportements imparfaits décrits ci-dessus, j'utilise une combinaison des trois méthodes: l'API de visibilité W3C, puis les méthodes de focus / flou et d'activité de l'utilisateur afin de réduire le taux de faux positifs. Cela permet de gérer les événements suivants:

  • Modification de l'onglet du navigateur en un autre (100% de précision, grâce à l'API W3C Page Visibility)
  • Page potentiellement masquée par une autre fenêtre, par exemple en raison de Alt + Tab (probabiliste = pas précis à 100%)
  • Attention de l'utilisateur potentiellement pas focalisée sur la page HTML (probabiliste = pas 100% précis)

Voici comment cela fonctionne: lorsque le document perd le focus, l'activité de l'utilisateur (telle que le déplacement de la souris) sur le document est surveillée afin de déterminer si la fenêtre est visible ou non. La probabilité de visibilité de la page est inversement proportionnelle à l'heure de la dernière activité de l'utilisateur sur la page: si l'utilisateur ne fait aucune activité sur le document pendant une longue période, la page n'est probablement pas visible. Le code ci-dessous imite l'API de visibilité de page W3C: il se comporte de la même manière mais a un petit taux de faux positifs. Il a l'avantage d'être multi-navigateurs (testé sur 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>

Puisqu'il n'existe actuellement aucune solution cross-browser fonctionnant sans faux positif, vous devriez réfléchir à deux fois sur la désactivation de l'activité périodique sur votre site web.


J'ai commencé à utiliser la réponse du wiki de la communauté, mais j'ai réalisé qu'il ne détectait pas les événements alt-tab dans Chrome. En effet, il utilise la première source d'événement disponible, et dans ce cas, il s'agit de l'API de visibilité de la page, qui, dans Chrome, ne semble pas suivre les alt-tabulations.

J'ai décidé de modifier un peu le script pour garder une trace de tous les événements possibles pour les changements de mise au point de la page. Voici une fonction que vous pouvez laisser tomber:

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

J'utiliserais jQuery parce que tout ce que vous avez à faire est:

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

Ou au moins cela a fonctionné pour moi.


Je voulais juste ajouter: La question n'est pas claire écrite. "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'y a pas de foyer. La plupart des systèmes de bureau peuvent 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" qui peut être très différent de "l'onglet n'a pas de focus".


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 cet exemple: <div react-on-window-focus="refresh()"> , où refresh() est une fonction de portée dans la portée de tout contrôleur dans la portée.


Si vous voulez agir sur tout le flou du navigateur : 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 à une autre page. Cela se déclenche également si vous travaillez sur des 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 réussi testé sur FF.


Un moyen un peu plus compliqué serait d'utiliser setInterval() pour vérifier la position de la souris et comparer avec la dernière vérification. Si la souris n'a pas bougé depuis un certain temps, l'utilisateur est probablement inactif.

Cela a l'avantage supplémentaire de dire si l'utilisateur est inactif, au lieu de simplement vérifier si la fenêtre n'est pas active.

Comme beaucoup de personnes l'ont souligné, ce n'est pas toujours un bon moyen de vérifier si la fenêtre de l'utilisateur ou du navigateur est inactive, car l'utilisateur n'utilise même pas la souris ou regarde une vidéo ou similaire. Je suggère simplement une façon possible de vérifier l'inactivité.


Utilisation de: MDN

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

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


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/





window