¿Cómo puedo detectar si un usuario ha llegado a una página utilizando el botón Atrás?

90

Esta pregunta es similar a Rastrear cuando el usuario presiona el botón Atrás en el navegador , pero no es lo mismo ... Tengo una solución y la estoy publicando aquí para referencia y comentarios. Si alguien tiene mejores opciones, ¡soy todo oídos!

La situación es que tengo una página con una "edición en el lugar", a la flickr. Es decir, hay un DIV "haga clic aquí para agregar una descripción", que al hacer clic se convierte en un TEXTAREA con los botones Guardar y Cancelar. Al hacer clic en Guardar, se publican los datos en el servidor para actualizar la base de datos y se coloca la nueva descripción en el DIV en lugar de TEXTAREA. Si se actualiza la página, se muestra la nueva descripción de la base de datos con una opción de "hacer clic para editar". Material web 2.0 bastante estándar en estos días.

El problema es que si:

  1. la página se carga sin la descripción
  2. el usuario agrega una descripción
  3. se navega desde la página haciendo clic en un enlace
  4. el usuario hace clic en el botón Atrás

Luego, lo que se muestra (del caché del navegador) es la versión de la página sin el DIV modificado dinámicamente que contiene la nueva descripción.

Este es un problema bastante grande ya que el usuario asume que su actualización se ha perdido y no necesariamente entenderá que necesita actualizar la página para ver los cambios.

Entonces, la pregunta es: ¿Cómo se puede marcar una página como modificada después de que se ha cargado, y luego detectar cuando el usuario "vuelve a ella" y forzar una actualización en esa situación?

Tom
fuente
¿En qué se diferencia esto de la pregunta que citó?
innaM
la pregunta es similar, pero creo que el entorno y, por lo tanto, la respuesta es diferente, podría estar equivocado. Mi interpretación de lo que puede ser un problema que se resolvería con la otra solución es: el usuario hace clic en una pestaña en una página que se carga con ajax, luego en otra pestaña y así sucesivamente. al hacer clic en el botón Atrás, volverá a una página diferente, no a la pestaña anterior. quieren volver a recorrer el "historial de ajax" dentro del "historial de la página". al menos esa es mi impresión de lo que se supone que debe hacer el Administrador del historial del navegador de Yahoo. Yo buscaba algo un poco más básico.
Tom
La respuesta aceptada presenta su truco de iframe.
innaM

Respuestas:

79

Utilice un formulario oculto. Los datos del formulario se conservan (normalmente) en los navegadores cuando recargas o presionas el botón Atrás para regresar a una página. Lo siguiente va en su página (probablemente cerca de la parte inferior):

<form name="ignore_me">
    <input type="hidden" id="page_is_dirty" name="page_is_dirty" value="0" />
</form>

En su javascript, necesitará lo siguiente:

var dirty_bit = document.getElementById('page_is_dirty');
if (dirty_bit.value == '1') window.location.reload();
function mark_page_dirty() {
    dirty_bit.value = '1';
}

El js que rastrea el formulario debe ejecutarse después de que el html se haya analizado por completo, pero puede colocar tanto el formulario como el js en línea en la parte superior de la página (segundo js) si la latencia del usuario es una preocupación seria.

Nick White
fuente
5
"Los datos del formulario se conservan (normalmente) en los navegadores", ¿puede ampliar eso? ¿no se conserva en algunos navegadores? o en algunas situaciones?
Tom
4
De hecho, permítanme enumerar todas las formas en que puedo pensar que esto puede salir mal. (1) Opera mantiene el estado completo de la página y no volverá a ejecutar js. (2) Si la configuración de la caché se modifica para forzar la carga de la página desde el servidor con el botón de retroceso, los datos del formulario pueden perderse, lo cual está bien para los propósitos de esta pregunta. (3) Los navegadores de teléfonos tienen un caché mucho más pequeño y es posible que el navegador ya haya eliminado la página del caché (no es un problema para estos fines).
Nick White
10
En algunos navegadores, las etiquetas ocultas no se conservan. Por lo tanto, podría usar una etiqueta de texto oculta en lugar de usar una etiqueta de entrada oculta. <input type = "text" value = "0" id = 'txt' name = 'txt' style = "display: none" />
sajith
2
Esto funciono muy bien para mi. Estoy seguro de que tiene situaciones en las que se rompe, pero todas las soluciones lo harán para este problema.
Joren
No funciona en iPhone con navegador Safari, inténtelo, cuando haga clic en el botón Atrás, no se volverá a llamar a js.
woheras
52

Aquí hay una solución moderna muy fácil para este viejo problema.

if (window.performance && window.performance.navigation.type === window.performance.navigation.TYPE_BACK_FORWARD) {
    alert('Got here using the browser "Back" or "Forward" button.');
}

window.performance actualmente es compatible con todos los principales navegadores.

Bassem
fuente
No había oído hablar de esto. Acabo de encontrar esta documentación ( developer.mozilla.org/en-US/docs/Web/API/Performance/navigation ) y me interesaría escucharlo más detalladamente (por ejemplo, ¿por qué es necesario en window.performance && window.performance.navigation.typelugar de solo window.performance.navigation.TYPE_BACK_FORWARD?).
Ryan
5
window.performance.navigation.TYPE_BACK_FORWARDes una constante que siempre es igual a 2. Por lo tanto, solo se usa para comparar.
Bassem
Performance.navigation es compatible con todos los dispositivos excepto andorid y opera mini, pero no hay problema, es muy agradable ( developer.mozilla.org/en-US/docs/Web/API/Performance/navigation )
Elbaz
1
Parece que performance.navigationestá en desuso y se sugiere utilizar en su lugar developer.mozilla.org/en-US/docs/Web/API/…
RubberDuckRabbit
1
Firefox v70 admitirá performance.navigation = 2para las pulsaciones del botón Atrás. Hasta v70, devolvió incorrectamente 1.
Andy Mercer
13

Este artículo lo explica. Consulte el código a continuación: http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/

<html>
    <head>
        <script>

            function pageShown(evt){
                if (evt.persisted) {
                    alert("pageshow event handler called.  The page was just restored from the Page Cache (eg. From the Back button.");
                } else {
                    alert("pageshow event handler called for the initial load.  This is the same as the load event.");
                }
            }

            function pageHidden(evt){
                if (evt.persisted) {
                    alert("pagehide event handler called.  The page was suspended and placed into the Page Cache.");
                } else {
                    alert("pagehide event handler called for page destruction.  This is the same as the unload event.");
                }
            }

            window.addEventListener("pageshow", pageShown, false);
            window.addEventListener("pagehide", pageHidden, false);

        </script>
    </head>
    <body>
        <a href="http://www.webkit.org/">Click for WebKit</a>
    </body>
</html>
Drew Baker
fuente
1
Esta es una buena respuesta, funciona en la última versión de Chrome 40 / Firefox 35. Sin embargo, IE11 no admite esos eventos.
Slava Abakumov
De acuerdo con caniuse.com/#feat=page-transition-events , los eventos de transición de página en realidad son compatibles con IE11 + Mientras hablamos, hay un 88% de cobertura global de la función
Cristian
FYI, Chrome devuelve event.persisted= false, incluso cuando se presiona desde el botón Atrás. Debe utilizar window.performance.navigationo PerformanceNavigationTiming para Chrome.
Andy Mercer
4

Para los navegadores modernos, esta parece ser la forma correcta ahora:

const perfEntries = performance.getEntriesByType('navigation');
if (perfEntries.length && perfEntries[0].type === 'back_forward') {
  console.log('User got here from Back or Forward button.');
}

Esto sigue siendo "experimental" a enero de 2020, pero parece tener un buen soporte.

https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming

getup8
fuente
1
mejor usar === en lugar de ==
Aboozar Rajabi
2

Como se mencionó anteriormente, encontré una solución y la estoy publicando aquí para referencia y comentarios.

La primera etapa de la solución es agregar lo siguiente a la página:

<!-- at the top of the content page -->
<IFRAME id="page_is_fresh" src="fresh.html" style="display:none;"></IFRAME>
<SCRIPT style="text/javascript">
  function reload_stale_page() { location.reload(); }
</SCRIPT>

El contenido de fresh.htmlno es importante, por lo que lo siguiente debería ser suficiente:

<!-- fresh.html -->
<HTML><BODY></BODY></HTML>

Cuando el código del lado del cliente actualiza la página, debe marcar la modificación de la siguiente manera:

function trigger_reload_if_user_clicks_back_button()
{
  // "dis-arm" the reload stale page function so it doesn't fire  
  // until the page is reloaded from the browser's cache
  window.reload_stale_page = function(){};

  // change the IFRAME to point to a page that will reload the 
  // page when it loads
  document.getElementById("page_is_fresh").src = "stale.html";
}

stale.htmlhace todo el trabajo: cuando se carga, llamará a la reload_stale_pagefunción que actualizará la página si es necesario. La primera vez que se carga (es decir, después de realizar la modificación, la reload_stale_pagefunción no hará nada).

<!-- stale.html -->
<HTML><BODY>
<SCRIPT type="text/javascript">window.parent.reload_stale_page();</SCRIPT>
</BODY></HTML>

De mis pruebas (mínimas) en esta etapa, esto parece funcionar como se desea. ¿He pasado por alto algo?

Tom
fuente
No que yo sepa, pero no lo he probado completamente. getElementById no es compatible con algunos navegadores más antiguos, pero es fácil de cambiar por jquery o algo así si es necesario.
Tom
2

Puedes resolverlo usando el evento onbeforeunload :

window.onbeforeunload = function () { }

Tener una función de administrador de eventos vacía onbeforeunload significa que la página se reconstruirá cada vez que se acceda. Los javascripts se volverán a ejecutar, los scripts del lado del servidor se volverán a ejecutar, la página se creará como si el usuario la estuviera presionando por primera vez, incluso si el usuario llegó a la página con solo presionar el botón de retroceso o avance. .

Aquí está el código completo de un ejemplo:

<html>
<head>
<title>onbeforeunload.html</title>
<script>
window.onbeforeunload = function () { }

function myfun()
{
   alert("The page has been refreshed.");
}
</script>

<body onload="myfun()">

Hello World!<br>

</body>
</html>

Pruébelo, navegue fuera de esta página y luego vuelva usando los botones "Atrás" o "Adelante" en su navegador.

Funciona bien en IE, FF y Chrome.

Por supuesto que puedes hacer lo que quieras dentro función myfun .

Más información: http://www.hunlock.com/blogs/Mastering_The_Back_Button_With_Javascript

Nes
fuente
Funciona en Safari para mí
Jakob
2

Puede usar localStorage o sessionStorage ( http://www.w3schools.com/html/html5_webstorage.asp ) para establecer una bandera (en lugar de usar un formulario oculto).

rerezz
fuente
su agradable solución, pero cómo lo hace si él quiere detectar esto cada vez, cuando abrí el sitio y la cerré, lo que necesito para detectar el momento en que se abrió y volver antes de cerrar
Elbaz
1

Usando esta página, especialmente incorporando el comentario de @sajith sobre la respuesta de @Nick White y esta página: http://www.mrc-productivity.com/techblog/?p=1235

<form name="ignore_me" style="display:none">
    <input type="text" id="reloadValue" name="reloadValue" value="" />
</form>

$(function ()
{
    var date = new Date();
    var time = date.getTime();

    if ($("#reloadValue").val().length === 0)
    {
        $("#reloadValue").val(time);
    }
    else
    {
        $("#reloadValue").val("");
        window.location.reload();
    }
});
nmit026
fuente
1

Aquí hay una versión de jQuery. Me he encontrado con la necesidad de usarlo varias veces debido a la forma en que Safari para escritorio / móvil maneja el caché cuando un usuario presiona el botón Atrás.

$(window).bind("pageshow", function(event) {
    if (event.originalEvent.persisted) {
        // Loading from cache
    }
});
Wes
fuente
Hasta donde yo sé, no parece funcionar en Chrome. Un console.log solo se activa en la declaración else (agregada para prueba) independientemente de cómo accedo a la página.
Luke
0

Hoy me encontré con un problema similar y lo resolví con localStorage (aquí con un poco de jQuery):

$(function() {

    if ($('#page1').length) {
        // this code must only run on page 1

        var checkSteps = function () {
            if (localStorage.getItem('steps') == 'step2') {
                // if we are here, we know that:
                // 1. the user is on page 1
                // 2. he has been on page 2
                // 3. this function is running, which means the user has submitted the form
                // 4. but steps == step2, which is impossible if the user has *just* submitted the form
                // therefore we know that he has come back, probably using the back button of his browser
                alert('oh hey, welcome back!');
            } else {
                setTimeout(checkSteps, 100);
            }
        };

        $('#form').on('submit', function (e) {
            e.preventDefault();
            localStorage.setItem('steps', 'step1'); // if "step1", then we know the user has submitted the form
            checkOrderSteps();
            // ... then do what you need to submit the form and load page 2
        });
    }

    if ($('#page2').length) {
        // this code must only run on page 2
        localStorage.setItem('steps', 'step2');
    }

});

Si básicamente:

En la página 1, cuando el usuario envía el formulario, establecemos un valor "pasos" en localStorage para indicar qué paso ha dado el usuario. Al mismo tiempo, lanzamos una función con timeout que comprobará si este valor ha sido modificado (por ejemplo, 10 veces / segundo).

En la página 2, cambiamos inmediatamente dicho valor.

Entonces, si el usuario usa el botón Atrás y el navegador restaura la página 1 en el estado exacto en que la dejamos, la función checkSteps aún se está ejecutando y es capaz de detectar que el valor en localStorage ha cambiado (y puede tomar la acción apropiada ). Una vez que esta verificación ha cumplido su propósito, no es necesario seguir ejecutándola, por lo que simplemente ya no usamos setTimeout.

s427
fuente