Evitar el desplazamiento del navegador en el estado emergente del historial de HTML5

83

¿Es posible evitar el comportamiento predeterminado de desplazarse por el documento cuando ocurre un evento popstate?

Nuestro sitio utiliza el desplazamiento animado de jQuery e History.js, y los cambios de estado deben desplazar al usuario a diferentes áreas de la página, ya sea a través de pushstate o popstate. El problema es que el navegador restaura la posición de desplazamiento del estado anterior automáticamente cuando ocurre un evento de estado emergente.

Intenté usar un elemento de contenedor configurado al 100% de ancho y alto del documento y desplazar el contenido dentro de ese contenedor. El problema con eso que he encontrado es que no parece ser tan suave como desplazarse por el documento; especialmente si usa muchos css3 como sombras de caja y degradados.

También intenté almacenar la posición de desplazamiento del documento durante un desplazamiento iniciado por el usuario y restaurarlo después de que el navegador se desplaza por la página (en popstate). Esto funciona bien en Firefox 12, pero en Chrome 19 hay un parpadeo debido a que la página se desplaza y se restaura. Supongo que esto tiene que ver con un retraso entre el desplazamiento y el evento de desplazamiento que se activa (donde se restaura la posición de desplazamiento).

Firefox desplaza la página (y activa el evento de desplazamiento) antes de que se active popstate y Chrome active primero popstate y luego se desplace por el documento.

Todos los sitios que he visto que usan la API de historial usan una solución similar a las anteriores o simplemente ignoran el cambio de posición de desplazamiento cuando un usuario retrocede / avanza (por ejemplo, GitHub).

¿Es posible evitar que el documento se desplace en absoluto en los eventos popstate?

Alex Malet de Carteret
fuente
Es posible que tenga cierto éxito al leer la dimensión de un elemento (para forzar un reajuste de página) después de establecer la posición de desplazamiento. por ejemplo, document.body.clientHeightpero no lo he visto funcionar en todos los navegadores.
Sean Hogan
¿Qué pasaría si manipularas la función de desplazamiento del documento? Ahí es donde comenzaría al menos. Hay una solución para ello aquí: stackoverflow.com/questions/7035331/…
Jeff Wooden

Respuestas:

66
if ('scrollRestoration' in history) {
  history.scrollRestoration = 'manual';
}

( Anunciado por Google el 2 de septiembre de 2015)

Soporte del navegador:

Chrome: compatible (desde 46)

Firefox: compatible (desde 46)

IE / Edge: no es compatible, solicite soporte (luego deje el enlace en el comentario)

Opera: compatible (desde 33)

Safari: compatible

Para obtener más información, consulte Compatibilidad del navegador en MDN .

Tamás Bolvári
fuente
1
Más corto: history.scrollRestoration && history.scrollRestoration = 'manual';o inclusovar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
Bharata
31

Este ha sido un problema reportado con el núcleo del desarrollador de mozilla durante más de un año . Desafortunadamente, el boleto no progresó realmente. Creo que Chrome es lo mismo: no hay una forma confiable de abordar la posición de desplazamiento a onpopstatetravés de js, ya que es el comportamiento nativo del navegador.

Sin embargo, hay esperanza para el futuro, si observa la especificación del historial de HTML5 , que desea explícitamente que la posición de desplazamiento se represente en el objeto de estado:

Los objetos de historial representan el historial de sesiones de su contexto de navegación como una lista plana de entradas del historial de sesiones. Cada entrada del historial de sesión consta de una URL y, opcionalmente, un objeto de estado, y además puede tener un título, un objeto de documento, datos de formulario, una posición de desplazamiento y otra información asociada.

Esto, y si lee los comentarios sobre el boleto de mozilla mencionado anteriormente, da alguna indicación de que es posible que en un futuro cercano la posición de desplazamiento ya no se restablezca onpopstate, al menos para las personas que usan pushState.

Desafortunadamente, hasta entonces, la posición de desplazamiento se almacena cuando pushStatese usa y replaceStateno reemplaza la posición de desplazamiento. De lo contrario, sería bastante fácil y podría utilizar replaceStatepara establecer la posición actual de desplazamiento cada vez que el usuario se desplaza por la página (con un gestor de desplazamiento prudente).

Además, desafortunadamente, la especificación HTML5 no especifica cuándo exactamente popstatese debe disparar el evento, solo dice: «se dispara en ciertos casos cuando se navega a una entrada del historial de sesión», lo que no dice claramente si es antes o después; si siempre fuera antes, sería posible una solución con el manejo del evento de desplazamiento que ocurre después del estado emergente.

¿Cancelar el evento de desplazamiento?

Además, también sería fácil, si el evento de desplazamiento fuera cancelable, que no lo es. Si lo fuera, podría cancelar el primer evento de desplazamiento de una serie (los eventos de desplazamiento del usuario son como lemmings, vienen en docenas, mientras que el evento de desplazamiento disparado por el reposicionamiento del historial es uno solo), y estaría bien.

No hay solución por ahora

Por lo que veo, lo único que recomendaría por ahora es esperar a que la especificación HTML5 se implemente por completo y seguir el comportamiento del navegador en este caso, eso significa: animar el desplazamiento cuando el navegador te permita hacerlo. y deje que el navegador cambie la posición de la página cuando haya un evento histórico. Lo único en lo que puede influir en cuanto a la posición es que use pushStatecuando la página está colocada en una buena forma para volver. Cualquier otra solución puede tener errores, o ser demasiado específica del navegador, o ambas cosas.

Beat Richartz
fuente
La redacción de la especificación histórica es ligeramente diferente ahora. Además, si se desplaza hacia abajo y mira la documentación de pushState, no se menciona agregar una posición de desplazamiento. La especificación sugiere que la entrada del historial de la sesión puede contener una posición de desplazamiento guardada, pero no estoy seguro de que eso signifique que el desarrollador web podrá configurarla.
Walex
4

Tendrás que usar algún tipo de rastreo de navegador horrible aquí. Para Firefox, iría con su solución de almacenar la posición de desplazamiento y restaurarla.

Pensé que tenía una buena solución de Webkit basada en su descripción, pero acabo de probar en Chrome 21, y parece que Chrome se desplaza primero, luego dispara el evento popstate y luego dispara el evento scroll. Pero como referencia, esto es lo que se me ocurrió:

function noScrollOnce(event) {
    event.preventDefault();
    document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
    document.addEventListener('scroll', noScrollOnce);
};​

Magia negra como fingir que la página se desplaza moviendo un absolute elemento posicionado, también queda descartada por la velocidad de repintado de la pantalla.

Entonces, estoy 99% seguro de que la respuesta es que no puede, y tendrá que usar uno de los compromisos que mencionó en la pregunta. Ambos navegadores se desplazan antes de que JavaScript sepa algo al respecto, por lo que JavaScript solo puede reaccionar después del evento. La única diferencia es que Firefox no pinta la pantalla hasta que se activa Javascript, razón por la cual existe una solución viable en Firefox pero no en WebKit.

Nathan MacInnes
fuente
3

Ahora puedes hacer

history.scrollRestoration = 'manual';

y esto debería evitar el desplazamiento del navegador. Esto solo funciona en este momento en Chrome 46 y superior, pero parece que Firefox también planea admitirlo

Roman Usherenko
fuente
2

La solución es usar position: fixedy especificar topigual a la posición de desplazamiento de la página.

Aquí hay un ejemplo:

$(window).on('popstate', function()
{
   $('.yourWrapAroundAllContent').css({
      position: 'fixed',
      top: -window.scrollY
   });

   requestAnimationFrame(function()
   {
      $('.yourWrapAroundAllContent').css({
         position: 'static',
         top: 0
      });          
   });
});

Sí, en su lugar, recibe una barra de desplazamiento parpadeante, pero es menos maligna.

mshipov
fuente
1

La siguiente solución debería funcionar en todos los navegadores.

Puede establecer la posición de desplazamiento en 0 en el evento de descarga. Puede leer sobre este evento aquí: https://developer.mozilla.org/en-US/docs/Web/Events/unload . Básicamente, el evento de descarga se activa justo antes de salir de la página.

Al establecer scrollPosition en 0 al descargar significa que cuando abandona la página con un pushState establecido, establece scrollPosition en 0. Cuando regresa a esta página actualizando o presionando atrás, no se desplazará automáticamente.

//Listen for unload event. This is triggered when leaving the page.
//Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload
window.addEventListener('unload', function(e) {
    //set scroll position to the top of the page.
    window.scrollTo(0, 0);
});
Ben Bud
fuente
1

Configurar scrollRestoration en manual no funcionó para mí, aquí está mi solución.

window.addEventListener('popstate', function(e) {
    var scrollTop = document.body.scrollTop;
    window.addEventListener('scroll', function(e) {
        document.body.scrollTop = scrollTop;
    });
});
rames
fuente
0

Cree un elemento 'span' en algún lugar en la parte superior de la página y establezca el foco en este en la carga. El navegador se desplazará hasta el elemento enfocado. Entiendo que esta es una solución alternativa y centrarse en "intervalo" no funciona en todos los navegadores (uhmmm .. Safari). Espero que esto ayude.

Prashant Saraswat
fuente
0

Esto es lo que he implementado en un sitio que quería que la posición de desplazamiento se enfocara en un elemento específico cuando se activa el estado posterior (botón de retroceso):

$(document).ready(function () {

     if (window.history.pushState) {
        //if push supported - push current page onto stack:
        window.history.pushState(null, document.title, document.location.href);
    }

    //add handler:
    $(window).on('popstate', PopStateHandler);
}

//fires when the back button is pressed:
function PopStateHandler(e) {
    emnt= $('#elementID');
    window.scrollTo(0, emnt.position().top);
    alert('scrolling to position');
}

Probado y funciona en firefox.

Chrome se desplazará a la posición pero luego se reposiciona de nuevo al lugar original.

George Filippakos
fuente
0

Como dijeron otros, no hay una forma real de hacerlo, solo una forma fea y hacky. Eliminar el detector de eventos de desplazamiento no funcionó para mí, así que esta es mi forma fea de hacerlo:

/// GLOBAL VARS ////////////////////////
var saveScrollPos = false;
var scrollPosSaved = window.pageYOffset;
////////////////////////////////////////

jQuery(document).ready(function($){

    //// Go back with keyboard shortcuts ////
    $(window).keydown(function(e){
        var key = e.keyCode || e.charCode;

        if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8)
        saveScrollPos = false;
    });
    /////////////////////////////////////////

    //// Go back with back button ////
    $("html").bind("mouseout",  function(){ saveScrollPos = false; });
    //////////////////////////////////

    $("html").bind("mousemove", function(){ saveScrollPos = true; });

    $(window).scroll(function(){
        if(saveScrollPos)
        scrollPosSaved = window.pageYOffset;
        else
        window.scrollTo(0, scrollPosSaved);
    });

});

Funciona en Chrome, FF e IE (parpadea la primera vez que regresa a IE). ¡Cualquier sugerencia de mejora es bienvenida! Espero que esto ayude.

pmrotule
fuente
-5

¿Quizás quieras probar esto?

window.onpopstate = function(event) {
  return false;
};
Dan Barzilay
fuente
3
Esto no funciona. Algunos eventos no se pueden prevenir y popstate es uno de ellos.
Nathan MacInnes