Cómo detectar el evento del botón Atrás del navegador - Navegador cruzado

218

¿Cómo detectas definitivamente si el usuario ha presionado o no el botón Atrás en el navegador?

¿Cómo se impone el uso de un botón de retroceso en la página dentro de una aplicación web de una sola página utilizando un #URLsistema?

¿Por qué demonios los botones de retroceso del navegador no activan sus propios eventos?

Xarus
fuente
2
Deseo que los eventos de navegación (hacia atrás / adelante) se agreguen en algún momento. Hasta ahora, esto es algo que está en desarrollo developer.mozilla.org/en-US/docs/Web/API/…
llaaalu

Respuestas:

184

(Nota: según los comentarios de Sharky, he incluido un código para detectar espacios en blanco)

Entonces, he visto estas preguntas con frecuencia en SO, y recientemente me he encontrado con el problema de controlar la funcionalidad del botón de retroceso. Después de unos días de buscar la mejor solución para mi aplicación (Single-Page with Hash Navigation), he encontrado un sistema simple, sin navegador, sin biblioteca para detectar el botón de retroceso.

La mayoría de las personas recomiendan usar:

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

Sin embargo, esta función también se llamará cuando un usuario use un elemento in-page que cambie el hash de ubicación. No es la mejor experiencia de usuario cuando su usuario hace clic y la página va hacia atrás o hacia adelante.

Para darle un resumen general de mi sistema, estoy completando una matriz con hashes anteriores a medida que mi usuario se mueve a través de la interfaz. Se parece a esto:

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

Muy claro. Hago esto para garantizar la compatibilidad entre navegadores, así como la compatibilidad con navegadores antiguos. Simplemente pase el nuevo hash a la función, y lo almacenará para usted y luego cambiará el hash (que luego se coloca en el historial del navegador).

También utilizo un botón de retroceso en la página que mueve al usuario entre páginas usando la lasthashmatriz. Se parece a esto:

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

Entonces esto moverá al usuario de regreso al último hash y eliminará ese último hash de la matriz (no tengo botón de avance en este momento).

Entonces. ¿Cómo detecto si un usuario ha utilizado mi botón de retroceso en la página o el botón del navegador?

Al principio miré window.onbeforeunload, pero fue en vano, eso solo se llama si el usuario va a cambiar de página. Esto no sucede en una aplicación de una sola página que utiliza navegación hash.

Entonces, después de un poco más de excavación, vi recomendaciones para tratar de establecer una variable de bandera. El problema con esto en mi caso es que trataría de configurarlo, pero como todo es asíncrono, no siempre se establecerá a tiempo para la instrucción if en el cambio de hash. .onMouseDownno siempre se llamaba al hacer clic, y agregarlo a un onclick nunca lo activaría lo suficientemente rápido.

Esto es cuando comencé a mirar la diferencia entre document, y window. Mi solución final fue establecer el indicador usando document.onmouseovery deshabilitarlo usando document.onmouseleave.

Lo que sucede es que mientras el mouse del usuario está dentro del área del documento (léase: la página representada, pero excluyendo el marco del navegador), mi valor booleano se establece en true. Tan pronto como el mouse abandona el área del documento, el booleano cambia a false.

De esta manera, puedo cambiar mi window.onhashchangea:

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

Notarás el cheque para #undefined. Esto se debe a que si no hay un historial disponible en mi matriz, regresa undefined. Lo uso para preguntarle al usuario si quiere irse usando un window.onbeforeunloadevento.

En resumen, y para las personas que no necesariamente usan un botón de retroceso en la página o una matriz para almacenar el historial:

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

Y ahí lo tienes. Una forma simple de tres partes para detectar el uso del botón de retroceso frente a los elementos en la página con respecto a la navegación hash.

EDITAR:

Para asegurarse de que el usuario no use la tecla de retroceso para activar el evento de retroceso, también puede incluir lo siguiente (Gracias a @thetoolman en esta pregunta ):

$(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
fuente
55
+1 buena idea, pero creo que esto fallará si el usuario usa cualquier método abreviado de teclado para "atrás" (tecla de retroceso en Firefox) mientras su mouse está dentro de la ventana del navegador
Sharky
3
Lo haré - Estoy en la oficina ahora de todos modos :) (EDITAR: Hecho ahora)
Xarus
77
¿Qué pasa con el móvil (por ejemplo, iPad)
Basarat
55
¿Qué pasa con los eventos de deslizamiento desde el panel táctil en MAC? ¿Podría eso ser capturado también?
Sriganesh Navaneethakrishnan
66
¿Cómo puedo diferenciar los botones de avance y retroceso?
Mr_Perfect
79

Puede probar el popstatecontrolador de eventos, por ejemplo:

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

Nota: Para obtener los mejores resultados, debe cargar este código solo en páginas específicas donde desea implementar la lógica para evitar cualquier otro problema inesperado.

El evento popstate se activa cada vez que cambia la entrada del historial actual (el usuario navega a un nuevo estado). Eso sucede cuando el usuario hace clic sobre el Back botones de avance o cuando de navegador / history.back(), history.forward(), history.go()métodos son llamados mediante programación.

La event.statepropiedad is del evento es igual al objeto de estado de historial.

Para la sintaxis de jQuery, envuélvala (para agregar incluso un oyente después de que el documento esté listo):

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

Consulte también: window.onpopstate en la carga de la página


Consulte también los ejemplos en Aplicaciones de una sola página y página 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>

Esto debería ser compatible con Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ y similares.

kenorb
fuente
2
Kenorb, tienes razón en que popstate funciona para tomar el cambio, pero no diferencia entre llamar programáticamente al evento y cuando el usuario hace clic en los botones del navegador.
Xarus
1
Gran publicación en aplicaciones de una sola página y HTML5 pushState . Perfecto para modernos "diseños de una página" o "aplicaciones de una sola página". Gracias por el enlace y su respuesta!
lowtechsun
1
e.state parece siempre indefinido en los navegadores modernos
FAjir
Le sugiero que ponga su código en un fragmento para que pueda ejecutarlo directamente aquí.
FourCinnamon0
16

Había estado luchando con este requisito durante bastante tiempo y tomé algunas de las soluciones anteriores para implementarlo. Sin embargo, me topé con una observación y parece funcionar en los navegadores Chrome, Firefox y Safari + Android y iPhone

En la carga de la página:

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

Déjeme saber si esto ayuda. Si me falta algo, estaré encantado de entenderlo.

Itzmeygu
fuente
1
No es verdad. eventtiene valor incluso para el botón de avance
Daniel Birowsky Popeski
14

En javascript, el tipo de navegación 2significa que se hace clic en el botón de retroceso o avance del navegador y el navegador está tomando el contenido de la memoria caché.

if(performance.navigation.type == 2)
{
    //Do your code here
}
Hasan Badshah
fuente
1
Esto no funciona en una aplicación de una sola página, el resultado es siempre 1 (lo que significa recargar). Además, no hace la diferencia entre los botones de retroceso y avance, lo cual es muy desafortunado.
papillon
"Además, no hace la diferencia entre el botón de retroceso y el de avance, lo cual es muy desafortunado". Sí, porque cada vez que los datos se obtienen de la memoria caché en ese momento, el performance.navigation.type es 2
Hasan Badshah
¡Eso no garantiza que funcione en todos los navegadores! así que deberías escribir algo como esto: if (window.performance) {// tu código relacionado con el rendimiento} también es mejor usar TYPE_BACK_FORWARD en lugar de 2 para mayor legibilidad.
blank94
7

Esto definitivamente funcionará (para detectar el clic del botón Atrás)

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

Mira esto:

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

funciona bien...

Jorge Rocha
fuente
No funciona en Chrome 78.0.3904.70 (Versión oficial) (64 bits)
Jeffz
Confirmado trabajando en Brave v1.3.118 >> Chromium: 80.0.3987.116 (versión oficial) (64 bits)
ZeroThe2nd
No funciona si llegaste a la página desde un sitio externo.
Milan Vlach
5
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

Esta es la única solución que funcionó para mí (no es un sitio web de una página). Funciona con Chrome, Firefox y Safari.

escanxr
fuente
1
window.performance.navigation ha quedado en desuso. Aquí están los documentos developer.mozilla.org/en-US/docs/Web/API/Performance/navigation
llaaalu
Esto funcionó para mí en mi página .cshtml. Trabajó con éxito en Chrome e IE. Gracias
Justjyde
5

La respuesta correcta ya está ahí para responder la pregunta. Quiero mencionar el nuevo JavaScript API PerformanceNavigationTiming , está reemplazando el obsoleto performance.navigation .

El siguiente código iniciará sesión en la consola "back_forward" si el usuario llegó a su página con el botón de retroceso o avance. Eche un vistazo a la tabla de compatibilidad antes de usarla en su proyecto.

var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
    console.log(perfEntries[i].type);
}
llaaalu
fuente
Esta no es una manera de saber si se hace clic en el botón Atrás en esta página . Indica si se hizo clic en la última página .
Seph Reed
Pero resultó ser la respuesta real que estaba buscando, ya que el problema que recibo es después de presionar el botón Atrás y no antes, lo que significa que puedo construir un código de respuesta que detiene mis problemas de solicitudes duplicadas. Gracias.
Andrew
1
Puede que esto no responda la pregunta, ¡pero exactamente lo que necesitaba! Agradable - no sabía de esto ...
Hogsmill
4

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

Para control móvil: 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>

Isa ilimitado
fuente
Esto no responde la pregunta formulada. La pregunta es sobre la detección del uso de un botón de retroceso en la página frente al botón de retroceso nativo del navegador.
Xarus
2
La pregunta está etiquetada javascript, no jquery.
skwny
3

Aquí está mi opinión al respecto. La suposición es que, cuando la URL cambia pero no se hace clic dentro de lo documentdetectado, se trata de un navegador hacia atrás (sí o hacia adelante). Un clic de los usuarios se restablece después de 2 segundos para que esto funcione en páginas que cargan contenido a través de 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
fuente
2

Pude usar algunas de las respuestas en este hilo y otras para que funcione en IE y Chrome / Edge. history.pushState para mí no era compatible con 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
fuente
Acabo de probar eso en Chrome 78.0.3904.70 (Versión oficial) (64 bits) y no funcionó.
Jeffz
2

Un componente completo puede implementarse solo si redefine la API (cambia los métodos del 'historial' del objeto) Compartiré la clase que acabo de escribir. Probado en Chrome y Mozilla Solo admite HTML5 y 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
fuente
Buen enfoque! Gracias por agregar a esto (todavía me parece sorprendente que haya una conversación sobre este hilo después de tantos años)
Xarus
Esto parece ser un mecanismo inteligente de seguimiento del historial. Sin embargo, el código no está comentado, por lo que es bastante difícil de seguir. Si alguien se aleja de la página actual, el código no detectará la presión de ese botón, lo que no responde a la pregunta original de "¿Cómo detecta definitivamente si el usuario presionó o no el botón de retroceso en el navegador?" Las pulsaciones de botones y el seguimiento del historial son dos cosas diferentes, aunque una puede activar la otra. El hecho de que todavía haya conversación indica una falla importante en el diseño del navegador web.
CubicleSoft
1

Document.mouseover no funciona para IE y Firefox. Sin embargo, he intentado esto:

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

Esto funciona para Chrome e IE y no para FireFox. Todavía estoy trabajando para que FireFox sea correcto. Cualquier forma fácil de detectar el clic del botón de retroceso / avance del navegador es bienvenida, no particularmente en JQuery sino también en AngularJS o Javascript simple.

Dhruv Gupta
fuente
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

        }
    });

El código anterior funcionó para mí. En los navegadores móviles, cuando el usuario hizo clic en el botón Atrás, queríamos restaurar el estado de la página según su visita anterior.

Ashwin
fuente
El código anterior funcionó para mí, en los navegadores móviles, cuando los usuarios hicieron clic en el botón Atrás, queríamos restaurar el estado de la página según su visita anterior
Ashwin
0

Lo resolví haciendo un seguimiento del evento original que activó el hashchange(ya sea un deslizamiento, un clic o una rueda), para que el evento no se confunda con un simple aterrizaje en la página y use una bandera adicional en cada uno de mis enlaces de eventos. El navegador no volverá a configurar la bandera al falsepresionar el botón Atrás:

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

fuente
-21

Probé las opciones anteriores pero ninguna de ellas me funciona. Aquí esta la solución

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

Consulte este enlace http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli para obtener más detalles

Sures Ramar
fuente
8
@MunamYousuf Es bastante obvio por qué este código no funciona realmente. Rastrea el botón de retroceso del navegador, pero el botón está fuera de la ventana gráfica, por lo tanto, las coordenadas clientX y clientY no deberían estar disponibles. Suponiendo que funciona, ¿qué tal iOS? El botón de retroceso está en la parte inferior ... También la forma en que está escrito es súper ineficiente, es condicional para cada evento desencadenado ... Dios, es tan malo que voy a darle un voto negativo.
James Wong - Restablece a Mónica el
¡Esto es horrible! ¿Por qué harías tal cosa? : / y ni siquiera funcionará como dijo James.
msqar