¿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?
fuente
document.body.clientHeight
pero no lo he visto funcionar en todos los navegadores.Respuestas:
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 .
fuente
history.scrollRestoration && history.scrollRestoration = 'manual';
o inclusovar sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
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
onpopstate
travé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:
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 usanpushState
.Desafortunadamente, hasta entonces, la posición de desplazamiento se almacena cuando
pushState
se usa yreplaceState
no reemplaza la posición de desplazamiento. De lo contrario, sería bastante fácil y podría utilizarreplaceState
para 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
popstate
se 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
pushState
cuando 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.fuente
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.
fuente
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
fuente
La solución es usar
position: fixed
y especificartop
igual 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.
fuente
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); });
fuente
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; }); });
fuente
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.
fuente
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.
fuente
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.
fuente
¿Quizás quieras probar esto?
window.onpopstate = function(event) { return false; };
fuente