¿Cómo puedo actualizar window.location.hash sin saltar el documento?

163

Tengo un panel deslizante configurado en mi sitio web.

Cuando terminó de animar, configuré el hash así

function() {
   window.location.hash = id;
}

(esta es una devolución de llamada y idse asigna anteriormente).

Esto funciona bien, para permitir al usuario marcar el panel como favorito, y también para que funcione la versión que no sea JavaScript.

Sin embargo, cuando actualizo el hash, el navegador salta a la ubicación. Supongo que este es el comportamiento esperado.

Mi pregunta es: ¿cómo puedo evitar esto? Es decir, ¿cómo puedo cambiar el hash de la ventana, pero no hacer que el navegador se desplace al elemento si existe el hash? ¿Algún tipo event.preventDefault()de cosa?

Estoy usando jQuery 1.4 y el complemento scrollTo .

¡Muchas gracias!

Actualizar

Aquí está el código que cambia el panel.

$('#something a').click(function(event) {
    event.preventDefault();
    var link = $(this);
    var id = link[0].hash;

    $('#slider').scrollTo(id, 800, {
        onAfter: function() {

            link.parents('li').siblings().removeClass('active');
            link.parent().addClass('active');
            window.location.hash = id;

            }
    });
});
alex
fuente
2
Supongo que lo has intentado event.preventDefault():)
Marko
@ Marko ¡No sé dónde colocarlo!
alex
¿Puedes publicar el resto de tu código?
Marko
@Marko Ivanovski No creo que sea relevante, pero veré qué puedo hacer.
alex
3
@Gareth No creo que haya un lugar para eso, porque sucede tan pronto como actualizo el hash.
alex

Respuestas:

261

Existe una solución alternativa mediante el uso de la API de historial en navegadores modernos con respaldo en los antiguos:

if(history.pushState) {
    history.pushState(null, null, '#myhash');
}
else {
    location.hash = '#myhash';
}

El crédito va a Lea Verou

Attila Fulop
fuente
Esta es la mejor respuesta, en mi opinión.
Greg Annandale
10
Tenga en cuenta que pushState tiene el efecto secundario (sangrado) de agregar un estado a la pila del historial del navegador. En otras palabras, cuando el usuario hace clic en el botón Atrás, no pasará nada a menos que también agregue un detector de eventos popstate.
David Cook
32
@DavidCook: también podríamos usar history.replaceState(que, para los cambios hash, podría tener más sentido) para evitar la necesidad de un popstatedetector de eventos.
Jack
Esto funcionó para mí. Me gusta el efecto del cambio de historia. Quiero agregar la advertencia de que esto no activará el hashchangeevento. Eso era algo que tenía que solucionar.
Jordania
¡advertencia! esto no desencadenará un hashchangeevento
santiago arizti
52

El problema es que está configurando window.location.hash en el atributo ID de un elemento. Es el comportamiento esperado para que el navegador salte a ese elemento, independientemente de si "preventDefault ()" o no.

Una forma de evitar esto es prefijar el hash con un valor arbitrario como este:

window.location.hash = 'panel-' + id.replace('#', '');

Luego, todo lo que necesita hacer es verificar el hash prefijado en la carga de la página. Como una ventaja adicional, incluso puede desplazarse suavemente hacia él, ya que ahora tiene el control del valor hash ...

$(function(){
    var h = window.location.hash.replace('panel-', '');
    if (h) {
        $('#slider').scrollTo(h, 800);
    }
});

Si necesita que esto funcione en todo momento (y no solo en la carga de la página inicial), puede usar una función para monitorear los cambios en el valor hash y saltar al elemento correcto sobre la marcha:

var foundHash;
setInterval(function() {
    var h = window.location.hash.replace('panel-', '');
    if (h && h !== foundHash) {
        $('#slider').scrollTo(h, 800);
        foundHash = h;
    }
}, 100);
Derek Hunziker
fuente
2
Esta es la única solución que realmente responde a la pregunta: permitir el hash sin saltar
amosmos
Esto realmente responde a la pregunta que explica la adición y eliminación del valor arbitrario para el hash para evitar el salto según el comportamiento predeterminado del navegador.
lowtechsun
1
buena solución, pero debilita la solución de OP ya que niega el uso de secciones de marcadores
Samus
27

Solución barata y desagradable ... ¡Usa el # feo! estilo.

Para configurarlo:

window.location.hash = '#!' + id;

Para leerlo:

id = window.location.hash.replace(/^#!/, '');

Dado que no coincide y ancla o id en la página, no saltará.

Gavin Brock
fuente
3
Tuve que preservar la compatibilidad con IE 7 y algunas versiones móviles del sitio. Esta solución fue más limpia para mí. En general, estaría en todas las soluciones pushState.
Eric Goodwin
1
establecer el hash con: window.location.hash = '#/' + id;y luego reemplazarlo con: window.location.hash.replace(/^#\//, '#');prettificará un poco la url -> projects/#/tab1
braitsch
13

¿Por qué no obtiene la posición de desplazamiento actual, colóquela en una variable, luego asigne el hash y vuelva a colocar la página en el lugar donde estaba:

var yScroll=document.body.scrollTop;
window.location.hash = id;
document.body.scrollTop=yScroll;

Esto debería funcionar

user706270
fuente
1
hm, este estaba trabajando para mí por un tiempo, pero en algún momento de los últimos meses esto ha dejado de trabajar en Firefox ....
matchew
10

Utilicé una combinación de la solución Attila Fulop (Lea Verou) para navegadores modernos y la solución Gavin Brock para navegadores antiguos de la siguiente manera:

if (history.pushState) {
    // IE10, Firefox, Chrome, etc.
    window.history.pushState(null, null, '#' + id);
} else {
    // IE9, IE8, etc
    window.location.hash = '#!' + id;
}

Como observó Gavin Brock, para capturar la identificación de nuevo, deberá tratar la cadena (que en este caso puede tener o no el "!") De la siguiente manera:

id = window.location.hash.replace(/^#!?/, '');

Antes de eso, probé una solución similar a la propuesta por el usuario 706270, pero no funcionó bien con Internet Explorer: como su motor Javascript no es muy rápido, puede notar que el desplazamiento aumenta y disminuye, lo que produce un efecto visual desagradable.

aldemarcalazans
fuente
2

Esta solución funcionó para mí.

El problema con la configuración location.hashes que la página saltará a esa identificación si se encuentra en la página.

El problema window.history.pushStatees que agrega una entrada al historial para cada pestaña en la que el usuario hace clic. Luego, cuando el usuario hace clic en el backbotón, va a la pestaña anterior. (Esto puede o no ser lo que quieres. No era lo que quería).

Para mí, replaceStatefue la mejor opción, ya que solo reemplaza el historial actual, por lo que cuando el usuario hace clic en el backbotón, va a la página anterior.

$('#tab-selector').tabs({
  activate: function(e, ui) {
    window.history.replaceState(null, null, ui.newPanel.selector);
  }
});

Consulte los documentos de API de historial en MDN.

Mike Vallano
fuente
1

No estoy seguro de si puede alterar el elemento original, pero ¿qué tal cambiar de usar el atributo id a algo más como data-id? Luego, solo lea el valor de data-id para su valor hash y no saltará.

Jon B
fuente
1

Esta solución me funcionó

// store the currently selected tab in the hash value
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
$('#myTab a[href="' + hash + '"]').tab('show');

Y mi código js completo es

$('#myTab a').click(function(e) {
  e.preventDefault();
  $(this).tab('show');
});

// store the currently selected tab in the hash value
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
  var id = $(e.target).attr("href").substr(1);
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }
   // window.location.hash = '#!' + id;
});

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
// console.log(hash);
$('#myTab a[href="' + hash + '"]').tab('show');
Rohit Dhiman
fuente
0

Al usar laravel framework, tuve algunos problemas con el uso de una función route-> back () ya que borró mi hash. Para mantener mi hash, creé una función simple:

$(function() {   
    if (localStorage.getItem("hash")    ){
     location.hash = localStorage.getItem("hash");
    }
}); 

y lo configuré en mi otra función JS de esta manera:

localStorage.setItem("hash", myvalue);

Puede nombrar sus valores de almacenamiento local de la forma que desee; mío llamado hash.

Por lo tanto, si el hash está configurado en PAGE1 y luego navega a PAGE2; el hash se volverá a crear en la PÁGINA1 cuando haga clic en Atrás en la PÁGINA2.

Andrés
fuente