Comment détecter l'événement du bouton de retour du navigateur - Navigateur croisé

219

Comment détectez-vous définitivement si l'utilisateur a ou non appuyé sur le bouton Retour du navigateur?

Comment imposez-vous l'utilisation d'un bouton de retour dans la page dans une application Web d'une seule page utilisant un #URLsystème?

Pourquoi diable les boutons de retour du navigateur ne déclenchent-ils pas leurs propres événements!?

Xarus
la source
2
Je souhaite que les événements de navigation (en arrière / en avant) soient ajoutés un jour. Jusqu'à présent, c'est quelque chose en cours de développement developer.mozilla.org/en-US/docs/Web/API/…
llaaalu

Réponses:

184

(Remarque: selon les commentaires de Sharky, j'ai inclus du code pour détecter les espaces arrière)

J'ai donc souvent vu ces questions sur SO, et j'ai récemment rencontré le problème du contrôle de la fonctionnalité du bouton de retour moi-même. Après quelques jours de recherche de la meilleure solution pour mon application (Single-Page with Hash Navigation), j'ai trouvé un système simple, sans navigateur et sans bibliothèque pour détecter le bouton de retour.

La plupart des gens recommandent d'utiliser:

window.onhashchange = function() {
 //blah blah blah
}

Cependant, cette fonction sera également appelée lorsqu'un utilisateur utilise un élément sur la page qui modifie le hachage d'emplacement. Pas la meilleure expérience utilisateur lorsque votre utilisateur clique et que la page recule ou avance.

Pour vous donner un aperçu général de mon système, je remplis un tableau avec des hachages précédents pendant que mon utilisateur se déplace dans l'interface. Cela ressemble à ceci:

function updateHistory(curr) {
    window.location.lasthash.push(window.location.hash);
    window.location.hash = curr;
}

Assez simple. Je fais cela pour assurer la prise en charge de plusieurs navigateurs, ainsi que la prise en charge des navigateurs plus anciens. Passez simplement le nouveau hachage à la fonction, et il le stockera pour vous, puis changera le hachage (qui sera ensuite mis dans l'historique du navigateur).

J'utilise également un bouton de retour dans la page qui déplace l'utilisateur entre les pages à l'aide du lasthashtableau. Cela ressemble à ceci:

function goBack() {
    window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
    //blah blah blah
    window.location.lasthash.pop();
}

Donc, cela ramènera l'utilisateur au dernier hachage et supprimera ce dernier hachage du tableau (je n'ai pas de bouton de transfert pour le moment).

Alors. Comment puis-je détecter si un utilisateur a utilisé mon bouton de retour sur la page ou le bouton du navigateur?

Au début, j'ai regardé window.onbeforeunload , mais en vain - cela n'est appelé que si l'utilisateur va changer de page. Cela ne se produit pas dans une application d'une seule page utilisant la navigation par hachage.

Donc, après quelques recherches supplémentaires, j'ai vu des recommandations pour essayer de définir une variable d'indicateur. Le problème avec cela dans mon cas, c'est que j'essaierais de le définir, mais comme tout est asynchrone, il ne serait pas toujours défini à temps pour l'instruction if dans le changement de hachage. .onMouseDownn'a pas toujours été appelé en cliquant, et l'ajouter à un onclick ne le déclencherait jamais assez rapidement.

C'est à ce moment que j'ai commencé à regarder la différence entre document, et window. Ma dernière solution a été de définir l'indicateur à l'aide de document.onmouseoveret de le désactiver à l'aide de document.onmouseleave.

Ce qui se passe, c'est que pendant que la souris de l'utilisateur se trouve à l'intérieur de la zone du document (lire: la page rendue, mais en excluant le cadre du navigateur), mon booléen est défini sur true. Dès que la souris quitte la zone du document, le booléen bascule vers false.

De cette façon, je peux changer mon window.onhashchangepour:

window.onhashchange = function() {
    if (window.innerDocClick) {
        window.innerDocClick = false;
    } else {
        if (window.location.hash != '#undefined') {
            goBack();
        } else {
            history.pushState("", document.title, window.location.pathname);
            location.reload();
        }
    }
}

Vous noterez le chèque #undefined. En effet, s'il n'y a pas d'historique disponible dans mon tableau, il revient undefined. J'utilise ceci pour demander à l'utilisateur s'il souhaite quitter en utilisant unwindow.onbeforeunload événement.

Donc, en bref, et pour les personnes qui n'utilisent pas nécessairement un bouton de retour dans la page ou un tableau pour stocker l'historique:

document.onmouseover = function() {
    //User's mouse is inside the page.
    window.innerDocClick = true;
}

document.onmouseleave = function() {
    //User's mouse has left the page.
    window.innerDocClick = false;
}

window.onhashchange = function() {
    if (window.innerDocClick) {
        //Your own in-page mechanism triggered the hash change
    } else {
        //Browser back button was clicked
    }
}

Et voila. un moyen simple en trois parties pour détecter l'utilisation du bouton de retour par rapport aux éléments de la page en ce qui concerne la navigation par hachage.

ÉDITER:

Pour vous assurer que l'utilisateur n'utilise pas de retour arrière pour déclencher l'événement de retour, vous pouvez également inclure les éléments suivants (Merci à @thetoolman sur cette question ):

$(function(){
    /*
     * this swallows backspace keys on any non-input element.
     * stops backspace -> back
     */
    var rx = /INPUT|SELECT|TEXTAREA/i;

    $(document).bind("keydown keypress", function(e){
        if( e.which == 8 ){ // 8 == backspace
            if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
                e.preventDefault();
            }
        }
    });
});
Xarus
la source
5
+1 bonne idée, mais je pense que cela échouera si l'utilisateur utilise le raccourci clavier pour "retour" (touche de retour arrière sur firefox) alors que sa souris est dans la fenêtre du navigateur
Sharky
3
Je le ferai - je suis au bureau en ce moment de toute façon :) (EDIT: terminé maintenant)
Xarus
7
Qu'en est-il du mobile (par exemple, iPad)
basarat
5
Qu'en est-il des événements de glisser depuis le trackpad dans MAC. Cela pourrait-il également être saisi?
Sriganesh Navaneethakrishnan
6
Comment différencier les boutons avant et arrière?
Mr_Perfect
79

Vous pouvez essayer le popstategestionnaire d'événements, par exemple:

window.addEventListener('popstate', function(event) {
    // The popstate event is fired each time when the current history entry changes.

    var r = confirm("You pressed a Back button! Are you sure?!");

    if (r == true) {
        // Call Back button programmatically as per user confirmation.
        history.back();
        // Uncomment below line to redirect to the previous page instead.
        // window.location = document.referrer // Note: IE11 is not supporting this.
    } else {
        // Stay on the current page.
        history.pushState(null, null, window.location.pathname);
    }

    history.pushState(null, null, window.location.pathname);

}, false);

Remarque: Pour de meilleurs résultats, vous devez charger ce code uniquement sur des pages spécifiques où vous souhaitez implémenter la logique pour éviter tout autre problème inattendu.

L'événement popstate est déclenché chaque fois que l'entrée d'historique actuelle change (l'utilisateur navigue vers un nouvel état). Cela se produit lorsque l'utilisateur clique sur Retour / boutons vers l' avant ou lors de votre navigateur history.back(), history.forward(),history.go() les méthodes sont appelées programatically.

La event.statepropriété is de l'événement est égale à l'objet d'état historique.

Pour la syntaxe jQuery, enveloppez-la (pour ajouter un écouteur pair une fois le document prêt):

(function($) {
  // Above code here.
})(jQuery);

Voir aussi: window.onpopstate au chargement de la page


Voir aussi les exemples sur applications à page unique et la page pushState HTML5 :

<script>
// jQuery
$(window).on('popstate', function (e) {
    var state = e.originalEvent.state;
    if (state !== null) {
        //load content with ajax
    }
});

// Vanilla javascript
window.addEventListener('popstate', function (e) {
    var state = e.state;
    if (state !== null) {
        //load content with ajax
    }
});
</script>

Cela devrait être compatible avec Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ et similaire.

Kenorb
la source
2
Kenorb, vous avez raison, popstate fonctionne pour saisir le changement, mais il ne fait pas de différence entre l'appel programmé de l'événement et lorsque l'utilisateur clique sur les boutons du navigateur.
Xarus
1
Excellent article sur les applications à page unique et le pushState HTML5 . Parfait pour les "mises en page d'une page" modernes ou les "applications d'une seule page". Merci pour le lien et votre réponse!
lowtechsun
1
e.state semble toujours indéfini dans les navigateurs modernes
FAjir
Je vous suggère de mettre votre code dans un extrait afin de pouvoir l'exécuter directement ici.
FourCinnamon0
16

Je luttais avec cette exigence depuis un bon moment et j'ai pris certaines des solutions ci-dessus pour la mettre en œuvre. Cependant, je suis tombé sur une observation et elle semble fonctionner sur les navigateurs Chrome, Firefox et Safari + Android et iPhone

Au chargement de la page:

window.history.pushState({page: 1}, "", "");

window.onpopstate = function(event) {

  // "event" object seems to contain value only when the back button is clicked
  // and if the pop state event fires due to clicks on a button
  // or a link it comes up as "undefined" 

  if(event){
    // Code to handle back button or prevent from navigation
  }
  else{
    // Continue user action through link or button
  }
}

Faites-moi savoir si cela aide. Si je manque quelque chose, je serai heureux de comprendre.

Itzmeygu
la source
1
Pas vrai. eventa de la valeur même pour le bouton avant
Daniel Birowsky Popeski
14

En javascript, le type de navigation 2signifie que le bouton Précédent ou Suivant du navigateur est cliqué et que le navigateur prend réellement le contenu du cache.

if(performance.navigation.type == 2)
{
    //Do your code here
}
Hasan Badshah
la source
1
Cela ne fonctionne pas sur une application d'une seule page, le résultat est toujours 1 (ce qui signifie recharger). De plus, cela ne fait pas la différence entre les boutons Précédent et Suivant, ce qui est très regrettable.
papillon
« En outre , il ne fait pas la différence entre l ' arrière et le bouton vers l' avant, ce qui est très regrettable. » Oui , car à chaque fois que les données sont extraites du cache que le temps est le performance.navigation.type 2
Hasan Badshah
Ce n'est pas garanti de fonctionner dans tous les navigateurs! vous devez donc écrire quelque chose comme ceci: if (window.performance) {// votre code lié aux performances}, il est également préférable d'utiliser TYPE_BACK_FORWARD au lieu de 2 pour plus de lisibilité.
blank94
7

Cela fonctionnera certainement (pour détecter le clic sur le bouton de retour)

$(window).on('popstate', function(event) {
 alert("pop");
});
rohan parab
la source
6

Regarde ça:

history.pushState(null, null, location.href);
    window.onpopstate = function () {
        history.go(1);
    };

ça fonctionne bien...

Jorge Rocha
la source
Ne fonctionne pas dans Chrome 78.0.3904.70 (version officielle) (64 bits)
Jeffz
Travail confirmé sur Brave v1.3.118 >> Chrome: 80.0.3987.116 (version officielle) (64 bits)
ZeroThe2nd
Ne fonctionne pas si vous êtes venu sur la page à partir d'un site externe.
Milan Vlach
5
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

C'est la seule solution qui a fonctionné pour moi (ce n'est pas un site Web en page). Cela fonctionne avec Chrome, Firefox et Safari.

escanxr
la source
1
window.performance.navigation est obsolète. Voici les docs developer.mozilla.org/en-US/docs/Web/API/Performance/navigation
llaaalu
Cela a fonctionné pour moi sur ma page .cshtml. A fonctionné avec succès sur Chrome et IE. Merci
Justjyde
5

La bonne réponse est déjà là pour répondre à la question. Je veux mentionner la nouvelle API JavaScript PerformanceNavigationTiming , elle remplace la performance.navigation obsolète .

Le code suivant se connectera à la console "back_forward" si l'utilisateur a atterri sur votre page en utilisant le bouton Précédent ou Suivant. Jetez un œil au tableau de compatibilité avant de l'utiliser dans votre projet.

var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
    console.log(perfEntries[i].type);
}
llaaalu
la source
Ce n'est pas un moyen de savoir si un bouton de retour est cliqué sur cette page . Il indique s'il a été cliqué sur la dernière page .
Seph Reed
Mais il se trouve que c'est la réponse que je cherchais, car le problème que j'obtiens est après avoir appuyé sur le bouton Retour et pas avant de pouvoir créer un code de réponse qui arrête mes problèmes de demandes en double. Merci.
Andrew
1
Cela peut ne pas répondre à la question, mais exactement ce dont j'avais besoin! Sympa - je ne savais pas ça ...
Hogsmill
4

Navigateur: https://jsfiddle.net/Limitlessisa/axt1Lqoz/

Pour le contrôle mobile: https://jsfiddle.net/Limitlessisa/axt1Lqoz/show/

$(document).ready(function() {
  $('body').on('click touch', '#share', function(e) {
    $('.share').fadeIn();
  });
});

// geri butonunu yakalama
window.onhashchange = function(e) {
  var oldURL = e.oldURL.split('#')[1];
  var newURL = e.newURL.split('#')[1];

  if (oldURL == 'share') {
    $('.share').fadeOut();
    e.preventDefault();
    return false;
  }
  //console.log('old:'+oldURL+' new:'+newURL);
}
.share{position:fixed; display:none; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.8); color:white; padding:20px;
<!DOCTYPE html>
<html>

<head>
    <title>Back Button Example</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

</head>

<body style="text-align:center; padding:0;">
    <a href="#share" id="share">Share</a>
    <div class="share" style="">
        <h1>Test Page</h1>
        <p> Back button press please for control.</p>
    </div>
</body>

</html>

Limitless isa
la source
Cela ne répond pas à la question posée. La question consiste à détecter l'utilisation d'un bouton de retour dans la page par rapport au bouton de retour natif du navigateur.
Xarus
2
La question est marquée javascript, pas jquery.
skwny
3

Voici mon point de vue. L'hypothèse est que lorsque l'URL change mais qu'il n'y a pas de clic dans le documentdétecté, c'est un navigateur en arrière (oui ou en avant). Un clic des utilisateurs est réinitialisé après 2 secondes pour que cela fonctionne sur les pages qui chargent du contenu via Ajax:

(function(window, $) {
  var anyClick, consoleLog, debug, delay;
  delay = function(sec, func) {
    return setTimeout(func, sec * 1000);
  };
  debug = true;
  anyClick = false;
  consoleLog = function(type, message) {
    if (debug) {
      return console[type](message);
    }
  };
  $(window.document).click(function() {
    anyClick = true;
    consoleLog("info", "clicked");
    return delay(2, function() {
      consoleLog("info", "reset click state");
      return anyClick = false;
    });
  });
  return window.addEventListener("popstate", function(e) {
    if (anyClick !== true) {
      consoleLog("info", "Back clicked");
      return window.dataLayer.push({
        event: 'analyticsEvent',
        eventCategory: 'test',
        eventAction: 'test'
      });
    }
  });
})(window, jQuery);
TomTom101
la source
2

J'ai pu utiliser certaines des réponses de ce fil et d'autres pour le faire fonctionner dans IE et Chrome / Edge. history.pushState pour moi n'était pas pris en charge dans IE11.

if (history.pushState) {
    //Chrome and modern browsers
    history.pushState(null, document.title, location.href);
    window.addEventListener('popstate', function (event) {
        history.pushState(null, document.title, location.href);
    });
}
else {
    //IE
    history.forward();
}
Arsalan Siddiqui
la source
Je viens d'essayer cela sur Chrome 78.0.3904.70 (version officielle) (64 bits) et cela n'a pas fonctionné.
Jeffz
2

Un composant à part entière ne peut être implémenté que si vous redéfinissez l'API (changez les méthodes de l'historique de l'objet). Je partagerai la classe qui vient d'être écrite. Testé sur Chrome et Mozilla Prise en charge uniquement HTML5 et ECMAScript5-6

class HistoryNavigation {
    static init()
    {
        if(HistoryNavigation.is_init===true){
            return;
        }
        HistoryNavigation.is_init=true;

        let history_stack=[];
        let n=0;
        let  current_state={timestamp:Date.now()+n};
        n++;
        let init_HNState;
        if(history.state!==null){
            current_state=history.state.HNState;
            history_stack=history.state.HNState.history_stack;
            init_HNState=history.state.HNState;
        } else {
            init_HNState={timestamp:current_state.timestamp,history_stack};
        }
        let listenerPushState=function(params){
            params=Object.assign({state:null},params);
            params.state=params.state!==null?Object.assign({},params.state):{};
            let h_state={ timestamp:Date.now()+n};
            n++;
            let key = history_stack.indexOf(current_state.timestamp);
            key=key+1;
            history_stack.splice(key);
            history_stack.push(h_state.timestamp);
            h_state.history_stack=history_stack;
            params.state.HNState=h_state;
            current_state=h_state;
            return params;
        };
        let listenerReplaceState=function(params){
            params=Object.assign({state:null},params);
            params.state=params.state!==null?Object.assign({},params.state):null;
            let h_state=Object.assign({},current_state);
            h_state.history_stack=history_stack;
            params.state.HNState=h_state;
            return params;
        };
        let desc=Object.getOwnPropertyDescriptors(History.prototype);
        delete desc.constructor;
        Object.defineProperties(History.prototype,{

            replaceState:Object.assign({},desc.replaceState,{
                value:function(state,title,url){
                    let params={state,title,url};
                    HistoryNavigation.dispatchEvent('history.state.replace',params);
                    params=Object.assign({state,title,url},params);
                    params=listenerReplaceState(params);
                    desc.replaceState.value.call(this,params.state,params.title,params.url);
                }
            }),
            pushState:Object.assign({},desc.pushState,{
                value:function(state,title,url){
                    let params={state,title,url};
                    HistoryNavigation.dispatchEvent('history.state.push',params);
                    params=Object.assign({state,title,url},params);
                    params=listenerPushState(params);
                    return desc.pushState.value.call(this, params.state, params.title, params.url);
                }
            })
        });
        HistoryNavigation.addEventListener('popstate',function(event){
            let HNState;
            if(event.state==null){
                HNState=init_HNState;
            } else {
                HNState=event.state.HNState;
            }
            let key_prev=history_stack.indexOf(current_state.timestamp);
            let key_state=history_stack.indexOf(HNState.timestamp);
            let delta=key_state-key_prev;
            let params={delta,event,state:Object.assign({},event.state)};
            delete params.state.HNState;
            HNState.history_stack=history_stack;
            if(event.state!==null){
                event.state.HNState=HNState;
            }
            current_state=HNState;
            HistoryNavigation.dispatchEvent('history.go',params);
        });

    }
    static addEventListener(...arg)
    {
        window.addEventListener(...arg);
    }
    static removeEventListener(...arg)
    {
        window.removeEventListener(...arg);
    }
    static dispatchEvent(event,params)
    {
        if(!(event instanceof Event)){
            event=new Event(event,{cancelable:true});
        }
        event.params=params;
        window.dispatchEvent(event);
    };
}
HistoryNavigation.init();

// exemple

HistoryNavigation.addEventListener('popstate',function(event){
    console.log('Will not start because they blocked the work');
});
HistoryNavigation.addEventListener('history.go',function(event){
    event.params.event.stopImmediatePropagation();// blocked popstate listeners
    console.log(event.params);
    // back or forward - see event.params.delta

});
HistoryNavigation.addEventListener('history.state.push',function(event){
    console.log(event);
});
HistoryNavigation.addEventListener('history.state.replace',function(event){
    console.log(event);
});
history.pushState({h:'hello'},'','');
history.pushState({h:'hello2'},'','');
history.pushState({h:'hello3'},'','');
history.back();

    ```
AlexeyP0708
la source
Belle approche! Merci d'avoir ajouté à cela (je trouve toujours étonnant qu'il y ait une conversation sur ce sujet après tant d'années)
Xarus
Cela semble être un mécanisme intelligent de suivi de l'historique. Cependant, le code n'est pas commenté, il est donc assez difficile à suivre. Si quelqu'un s'éloigne de la page actuelle, le code ne détectera pas cette pression sur le bouton, ce qui ne répond pas à la question initiale de "Comment pouvez-vous détecter définitivement si l'utilisateur a appuyé sur le bouton de retour dans le navigateur?" Les pressions sur les boutons et le suivi de l'historique sont deux choses différentes, même si l'une peut déclencher l'autre. Le fait qu'il y ait encore une conversation indique une faille majeure dans la conception du navigateur Web.
CubicleSoft
1

Le document.mouseover ne fonctionne pas pour IE et FireFox. Cependant, j'ai essayé ceci:

$(document).ready(function () {
  setInterval(function () {
    var $sample = $("body");
    if ($sample.is(":hover")) {
      window.innerDocClick = true;
    } else {
      window.innerDocClick = false;
    }
  });

});

window.onhashchange = function () {
  if (window.innerDocClick) {
    //Your own in-page mechanism triggered the hash change
  } else {
    //Browser back or forward button was pressed
  }
};

Cela fonctionne pour Chrome et IE et non pour FireFox. Toujours en train de faire fonctionner FireFox. Tout moyen facile de détecter le clic du bouton Précédent / Suivant du navigateur est le bienvenu, pas particulièrement dans JQuery mais aussi AngularJS ou Javascript simple.

Dhruv Gupta
la source
1
 <input style="display:none" id="__pageLoaded" value=""/>


 $(document).ready(function () {
        if ($("#__pageLoaded").val() != 1) {

            $("#__pageLoaded").val(1);


        } else {
            shared.isBackLoad = true;
            $("#__pageLoaded").val(1);  

            // Call any function that handles your back event

        }
    });

Le code ci-dessus a fonctionné pour moi. Sur les navigateurs mobiles, lorsque l'utilisateur a cliqué sur le bouton de retour, nous avons voulu restaurer l'état de la page comme lors de sa précédente visite.

Ashwin
la source
Le code ci-dessus a fonctionné pour moi, sur les navigateurs mobiles, lorsque les utilisateurs ont cliqué sur le bouton Retour, nous voulions restaurer l'état de la page comme lors de sa précédente visite
Ashwin
0

Je l'ai résolu en gardant une trace de l'événement d'origine qui a déclenché le hashchange(que ce soit un balayage, un clic ou une roue), afin que l'événement ne soit pas confondu avec un simple atterrissage sur la page, et en utilisant un indicateur supplémentaire dans chacune de mes liaisons d'événement. Le navigateur ne remettra pas le drapeau sur falselorsque vous appuyez sur le bouton de retour:

var evt = null,
canGoBackToThePast = true;

$('#next-slide').on('click touch', function(e) {
    evt = e;
    canGobackToThePast = false;
    // your logic (remember to set the 'canGoBackToThePast' flag back to 'true' at the end of it)
}

la source
-21

J'ai essayé les options ci-dessus mais aucune ne fonctionne pour moi. Voici la solution

if(window.event)
   {
        if(window.event.clientX < 40 && window.event.clientY < 0)
        {
            alert("Browser back button is clicked...");
        }
        else
        {
            alert("Browser refresh button is clicked...");
        }
    }

Reportez-vous à ce lien http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli pour plus de détails

Sures Ramar
la source
8
@MunamYousuf Il est assez évident pourquoi ce morceau de code ne fonctionne pas vraiment. Il suit le bouton de retour du navigateur mais le bouton est en dehors de la fenêtre d'affichage, donc les coordonnées clientX et clientY ne devraient pas être disponibles. En supposant que cela fonctionne, que diriez-vous d'iOS? Le bouton de retour est en bas ... De plus, la façon dont il est écrit est super inefficace, c'est une condition pour chaque événement déclenché ... Dieu c'est tellement mauvais que je vais lui donner un vote négatif.
James Wong - Rétablir Monica
C'est horrible! pourquoi feriez-vous une telle chose? : / et ça ne marchera même pas comme James l'a dit.
msqar