Como posso atualizar o window.location.hash sem pular o documento?

163

Eu tenho um painel deslizante configurado no meu site.

Quando terminou de animar, defini o hash da seguinte maneira

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

(trata-se de um retorno de chamada e idé atribuído anteriormente).

Isso funciona bem, para permitir que o usuário adicione o painel aos favoritos e também para que a versão não JavaScript funcione.

No entanto, quando eu atualizo o hash, o navegador pula para o local. Eu acho que esse é um comportamento esperado.

Minha pergunta é: como posso evitar isso? Ou seja, como posso alterar o hash da janela, mas não fazer com que o navegador role para o elemento se o hash existir? Algum tipo event.preventDefault()de coisa?

Estou usando o jQuery 1.4 e o plugin scrollTo .

Muito Obrigado!

Atualizar

Aqui está o código que altera o painel.

$('#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
fonte
2
Eu suponho que você já tentou event.preventDefault():)
Marko
@ Marko Eu não sei onde colocá-lo!
6288 alex
Você pode postar o restante do seu código?
Marko
@ Marko Ivanovski Não acho relevante, mas vou ver o que posso fazer.
6281 alex
3
@ Gareth Acho que não há lugar para isso, porque acontece assim que eu atualizo o hash.
6101 alex

Respostas:

261

Existe uma solução alternativa usando a API de histórico em navegadores modernos com fallback nos antigos:

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

Crédito vai para Lea Verou

Attila Fulop
fonte
Esta é a melhor resposta, na minha opinião.
Greg Annandale
10
Observe que o pushState tem o efeito colateral (recuado) de adicionar um estado à pilha de histórico do navegador. Em outras palavras, quando o usuário clicar no botão Voltar, nada acontecerá, a menos que você também adicione um ouvinte de evento popstate.
David Cook
32
@ DavidCook - Também poderíamos usar history.replaceState(o que, para alterações de hash, pode fazer mais sentido) para evitar a necessidade de um popstateouvinte de evento.
Jack
Isso funcionou para mim. Eu gosto do efeito da mudança da história. Quero acrescentar a ressalva de que isso não acionará o hashchangeevento. Isso era algo que eu tinha que resolver.
Jordan
Aviso! isso não vai desencadear um hashchangeevento
santiago Arizti
52

O problema é que você está configurando window.location.hash para o atributo de ID de um elemento. É o comportamento esperado para o navegador pular para esse elemento, independentemente de você "preventDefault ()" ou não.

Uma maneira de contornar isso é prefixar o hash com um valor arbitrário da seguinte maneira:

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

Em seguida, tudo o que você precisa fazer é verificar o hash prefixado no carregamento da página. Como um bônus adicional, você pode até rolar suavemente até ele, agora que você está no controle do valor do hash ...

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

Se você precisar que isso funcione o tempo todo (e não apenas no carregamento inicial da página), você pode usar uma função para monitorar as alterações no valor do hash e pular para o elemento correto rapidamente:

var foundHash;
setInterval(function() {
    var h = window.location.hash.replace('panel-', '');
    if (h && h !== foundHash) {
        $('#slider').scrollTo(h, 800);
        foundHash = h;
    }
}, 100);
Derek Hunziker
fonte
2
Esta é a única solução que realmente responde à pergunta - permitindo hashing sem salto
amosmos
Isso realmente responde à pergunta que explica a adição e exclusão do valor arbitrário do hash para evitar o salto conforme o comportamento padrão do navegador.
Lowtechsun 27/05
1
boa solução, mas enfraquece a solução PO, uma vez que nega o uso de seções bookmarking
Samus
27

Solução barata e desagradável .. Use o feio #! estilo.

Para configurá-lo:

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

Para ler:

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

Como ele não corresponde à âncora ou ao ID da página, ele não salta.

Gavin Brock
fonte
3
Eu tive que preservar a compatibilidade com o IE 7 e algumas versões móveis do site. Esta solução foi mais limpa para mim. Geralmente eu estaria em todas as soluções pushState.
Eric Goodwin
1
definindo o hash com: window.location.hash = '#/' + id;e, em seguida, substituí-lo com: window.location.hash.replace(/^#\//, '#');vai embelezar a url um pouco -> projects/#/tab1
braitsch
13

Por que você não obtém a posição atual de rolagem, coloque-a em uma variável, atribua o hash e coloque a rolagem da página de volta para onde estava:

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

isso deve funcionar

user706270
fonte
1
hm, este estava trabalhando para mim por algum tempo, mas em algum momento nos últimos meses este parou de funcionar no firefox ....
matchew
10

Usei uma combinação da solução Attila Fulop (Lea Verou) para navegadores modernos e a solução Gavin Brock para navegadores antigos da seguinte maneira:

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

Conforme observado por Gavin Brock, para capturar o id de volta, você terá que tratar a string (que neste caso pode ter ou não o "!") Da seguinte maneira:

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

Antes disso, tentei uma solução semelhante à proposta pelo user706270, mas não funcionou bem com o Internet Explorer: como o mecanismo Javascript não é muito rápido, você pode observar o aumento e a diminuição da rolagem, o que produz um efeito visual desagradável.

aldemarcalazans
fonte
2

Esta solução funcionou para mim.

O problema com a configuração location.hashé que a página passará para esse ID se for encontrada na página.

O problema window.history.pushStateé que ele adiciona uma entrada ao histórico para cada guia em que o usuário clica. Então, quando o usuário clica no backbotão, eles vão para a guia anterior. (isso pode ou não ser o que você deseja. não era o que eu queria).

Para mim, replaceStatefoi a melhor opção, pois substitui apenas o histórico atual; portanto, quando o usuário clica no backbotão, eles vão para a página anterior.

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

Confira os documentos da API do histórico no MDN.

Mike Vallano
fonte
1

Não tenho certeza se você pode alterar o elemento original, mas que tal mudar de usar o id attr para algo mais como data-id? Em seguida, basta ler o valor do ID de dados para o seu valor de hash e ele não irá pular.

Jon B
fonte
1

Esta solução funcionou para mim

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

E meu código js completo é

$('#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
fonte
0

Ao usar o laravel framework, tive alguns problemas com o uso de uma função route-> back (), pois ela apagou meu hash. Para manter meu hash, criei uma função simples:

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

e defini-o na minha outra função JS como esta:

localStorage.setItem("hash", myvalue);

Você pode nomear seus valores de armazenamento local da maneira que desejar; meu chamado hash.

Portanto, se o hash estiver definido na PAGE1 e você navegar para PAGE2; o hash será recriado na PAGE1 quando você clicar em Voltar na PAGE2.

Andrew
fonte