Mudando a barra de endereço do browser sem refresh

Hoje vamos falar sobre um recurso bastante interessante dos nossos browsers que nos permite fazer a alteração das informações das páginas que acessamos, inclusive a URL que é mostrada na barra de endereço do seu browser sem fazer o uso do refresh e o melhor: mantendo o histórico.

Manipulando o histórico do browser

Para se fazer o que é proposto neste post é preciso entender como funciona a manipulação do histórico do nosso browser e os métodos que o objeto DOM window nos proporciona para manipular o nosso histórico. Neste post vamos falar especificamente sobre o window.history.

W3C implementa History com a seguinte interface:

interface History {
  readonly attribute long length;
  readonly attribute any state;
  void go(optional long delta);
  void back();
  void forward();
  void pushState(any data, DOMString title, optional DOMString url);
  void replaceState(any data, DOMString title, optional DOMString url);
};

Neste post, vou falar somente somente os métodos pushState e replaceState que são os métodos responsáveis por manipular o histórico da sessão do seu navegador.

Sempre que você abre uma nova aba e/ou janela o navegador inicia uma nova sessão. E é nesta sessão que ele armazena todas as URL’s que você visitou.

Método history.pushState

O método pushState registra uma nova entrada no seu histórico de sessão, mantendo o histórico. E essa é a sua sintaxe:

window.history.pushState(data, title [, url ] )

Data: O parâmetro data pode ser útil caso você queira utilizar o evento onPopState. O evento onPopState é invocado sempre que uma nova entrada é registrada no seu histórico de sessão.
Title: É o título da página que você quer que esta entrada tenha.
URL: É a URL que você quer que a página tenha. Você pode utilizar este parametro de duas formas:

  1. Absoluta: Passando toda a nova URL completa, incluindo protocolo, host, path etc. Ex: http://blog.igorescobar.com/
  2. Relativa: A URL que você passar será relativa a URL atual, ou seja, se você estiver acessando o http://blog.igorescobar.com/ e passar “/category/javascript/” a URL que será registrada é “http://blog.igorescobar.com/category/javascript/”.

Exemplo

 window.history.pushState('Object', 'Categoria JavaScript', '/category/javascript/'); 

O resultado deste código vai fazer com que a URL e o título da página que você estiver navegando mude, sem que o refresh ocorra. E se você apertar o “voltar” no seu navegador, vai ver que ele vai voltar a página anterior que você tinha acessado, ou seja, o histórico foi mantido.

Método history.replaceState

O método replaceState é muito parecido com o pushState. A única diferença entre os dois métodos é que o pushState armazena uma nova entrada, mantendo o histórico. O replaceState não, ele substituí a entrada do histórico de sessão atual pelos dados que você passa. E essa é a sua sintaxe:

window.history.replaceState(data, title [, url ] )

Exemplo:

window.history.replaceState('Object', 'Titulo da Página', '/outra-nova-url');

Evento onPopState

O evento onpopstate é invocado sempre que uma nova entrada é dada no histórico de sessão do seu browser. A forma como você pode utiliza-lo é só adaptar à sua necessidade de negócio. Uma forma de utilização deste evento é delegar para ele a responsabilidade de carregar via ajax todo o link que for clicado, por exemplo.

Exemplo de navegação sem refresh

Todo mundo já conhece o GitHub, certo? (espero que sim!) O GitHub faz do uso deste mesmo recurso para que vocês consigam visualizar os arquivos de um repositório de maneira rápida, sem refresh e mantendo um histórico da navegação. Veja que sempre que você clica em um arquivo, ele é carregado dentro do mesmo contexto, a url da página muda e você consegue ir para frente e voltar no histórico, graças ao método window.history.pushState.

Este é um exemplo de como seria a implementação de uma navegação parecida com jQuery.

$('#menu-nav a')​​.click(function(e){
  e.preventDefault();
  window.history.pushState({url: "" + $(this).attr('href') + ""}, $(this).attr('title') , $(this).attr('href'));
 });​

$(window).bind("popstate", function(e) {
  $('#my-navigation-container').load(e.state.url);
});

Compatibilidade

Os browsers Chrome, Opera, Safari e Firefox 4+ implementam todos estes métodos de forma nativa. Os browsers antigos utilizam o location.hash para imitar o comportamento. Existe uma biblioteca chamada History.js que implementa esta funcionalidade de forma crossbrowser.

 

software engineer @hole19golf, #javascript #nodejs #ruby #java #php #scala #python

Twitter LinkedIn 

Be Sociable, Share!

Posts Relacionados:

38 thoughts on “Mudando a barra de endereço do browser sem refresh

  1. Muito bom! Vou fazer alguns testes aqui e experimentar essa maravilha! 😀

  2. Aparentemente, não funciona com eventos show/hide, pois sempre que utilizo o botão voltar, o navegador não mantém as divs expandidas com animate, nem os conteúdos carregados pelo método .load e visualizados por FadeIn, ou seja a barra de endereços é alterada, mas a visualização do conteúdo é zerada, como na página inicial. Será que alguém tem alguma idéia de como resolver isso?

  3. Na página pai estou usando masonry e cycle, o projeto como um todo está funcional (já dá pra por no ar com o que tenho), mas ficaria completo se eu pudesse gerar sessões, pra poder individualizar cada conteúdo aberto com endereço (podendo adicionar individualmente ao favoritos e inserir comentários (facebook) em cada página carregada via ajax.

    Meu projeto é semelhante ao do cargo collective (salvo que o código que estou trabalhando é muito menor e mais simples). O funcionamento na íntegra envolve (masonry (para posicionamento dos grids), cycle (para o slide das imagens), ScrollTo (para posição automática da barra de rolagem, quando escolhido um projeto) + histórico/sessão)…o exemplo mais simples que encontrei: http://archive.nandocosta.com/

    resumindo: A idéia do projeto (resumida) é bem simples: Eu tenho um grid com diversos boxes, tenho uma função condicional que expande(animate) o box, exibe (show) uma div oculta, e puxa um html (.load) pelo id do box clicado. Até o momento não consegui integrar ScrollTo e histórico/sessão para os conteúdos dinâmicos.
    Sou iniciante em Jquery…e não consegui encontrar ninguém que tenha enfrentado este problema. Então qualquer dica nesse sentido é bem vinda.

  4. Entendi,

    Mas eu não vejo nenhum razão para acontecer o que você está vivenciando. Tente extrair esta funcionalidade em uma página com menos recursos. Talvez isso esteja acontecendo em função de algum plugin. Experimente também utilizar o plugin que eu citei no post, pode te ajudar caso você esteja tendo algum problema na implementação.

    – Igor

  5. Já tentei usar o jquery history, além de não ter funcionado, estou tentando diminuir a quantidade de plugins necessários, por isso já eliminei o ScrollTo.

    Após ler a documentação do html5, estou chegando a conclusão que o problema é no onPopState (que não sei onde chamar, se antes ou depois do eventro Load, ou como disparador do mesmo), e o fato de estar chamando a partir de um elemento, e não de um link está me confundindo totalmente.Segue o trecho do código em que (onPopState) está ausente:

    ** contexto de um box expandido por animate**

    $(this).find(‘.expandable’).fadeIn(‘slow’); //mostra div de conteúdo

    $(this).find(‘.preloader’)
    .ajaxStart(function(){
    $(this).fadeIn(); //inicia o preloader de conteúdo

    }).ajaxStop(function(){
    window.history.pushState(‘Object’, null, url+”) //altera o endereço ao terminar
    $(this).delay(1000).fadeOut();});

    //abaixo é onde devo inserir o onpopstate??? Se confirmado, como referenciar o pushState que já foi realizado? é necessário onpopState pra página inicial?

    $(this).find(‘.projectview’).delay(1000).fadeIn(‘slow’).load(target+’.html’); return false;

    // a página é chamada na div projectview, dentro da div expandable.

    desde já agradeço a atenção prestada, Igor.

  6. @Danilo,

    Eu faria da seguinte forma. Primeiro faça todo o fluxo funcionar, sem a mudança da URL sem refresh. Depois de tudo funcionando, você pode incrementar isso.

    Eu colocaria o pushState no callback do $.load. Este recurso só está disponível de forma default nos navegadores decentes, o resto implementa de uma forma diferente.

    Você só deve utilizar o onpopstate se você quiser fazer algum aproveitamento da informação que você está “pushando” no history.

    Usando o onpopstate você pode fazer a dinamica um pouco diferente.

    // Switch to the item
    window.history.pushState({ id: 35 }, ‘Viewing item #35’, ‘/item/35’});

    window.onpopstate = function (e) {
    var id = e.state.id;
    load_item(id);
    };

  7. @Danilo, outra coisa, se você olhar na documentação do ajaxStart e do ajaxStop, talvez ela não vai funcionar bem para o propósito que você está buscando. O ajaxStart só é invocado no primeiro request, nos outros ele não é chamado, o mesmo para o ajaxStop… então cuidado.

  8. O fluxo está ok, funcionando sem refresh e sem mudança de URL.
    quando uso o pushstate, ele altera a URL, mas não faz mais nada.
    Se um box estiver expandido, ele continua expandido, se estiver compactado continua compactado, é como se o botão voltar funcionasse apenas pra mudar a url,e não o estado da janela.

  9. quanto ao .ajaxStart/Stop…SE fecho um box ou abro outro box, ele limpa o html puxado(pra evitar que o conteúdo (vídeo,iframe, e os scripts do cycle continuem em execução) ou seja, toda vez que o usuário clica em um box, ele gera uma nova requisição ajax (o método .ajax, aparentemente tem funcionado aqui todas as vezes, visto estar testando apenas pelo localhost).

  10. Alterei o pushstate como callback da chamada ajax…realmente faz muito mais sentido. A minha dúvida com relação ao onPopState, é justamente se ele é método que fará a janela entender que quando clico no botão voltar, ele deve voltar à sessão anterior, com o box (y) expandido e carregado com o conteúdo (x). Estou vendo alguns comentários por aí, de pessoas usando um listener pra onpopstate pra gravar a sessão (pelo menos foi o que eu entendi). Mas na real…tá muito complicado…

    Se for um problema de plugin, com certeza é do masonry (chato pra diabo)….mas necessário a esse projeto,a o cronograma disponível e ao meu nível de conhecimento. Tá mais fácil abdicar do endereçamento, favoritos e comentários facebook, do que do plugin, que mantém a estrutura do layout (grid) líquido, pois as expansões dos boxes são customizáveis.

  11. Segue a documentação na MDN falando sobre o OnPopState:
    https://developer.mozilla.org/en/DOM/window.onpopstate

    – O onpopstate não é invocado quando que você utiliza o pushState ou o replaceState.
    – O evento popstate só é chamado quando fazemos alguma ação no browser, como ao clicar no botão voltar ou history.back() em javascript.

    Ou seja, respondendo suas dúvidas, você precisa estar preparado para carregar o conteúdo de três formas:

    1 – Quando o usuário clica em um link.
    2 – Quando o usuário utiliza o back ou forward do browser. (onpopstate)
    3 – Quando o usuário acessa a URL diretamente, sem passar pela fluxo de navegação comum, como se ele estivesse vindo do Google ou outro buscador.

    Mas o problema original que você apontou, não acredito que tenha relação com o uso do pushState, replaceState ou onPopState.

  12. Entendi.
    Vc tem idéia de algum link específico onde encontro exemplos da aplicação das formas 2 e 3 que você citou?
    Quanto ao problema original, vou realizar mais alguns testes e publicar, assim que o fizer, mando o link pra você…mando aqui mesmo ou por email (que não consegui localizar)?
    Obrigado Igor, pela clareza das explicações e ajuda.

  13. @Danilo,

    Não tenho, teria que estar fazendo utilizando um fiddle ou algo do tipo. Quando você terminar ou não, tenta dar uma jeito de colocar uma versão JS que você possa publica na web e postar aqui para gente. Dou uma olhada e tendo te ajudar.

    De nada 🙂

    – Igor

  14. @Danilo

    Fiz um fiddle bem simples, utilizando somente Javascript. Ele mostra a página atual que está aberta e foca no link que ele está acessando. Fiz um efeito no link para mostrar que o efeito é mantido e que nenhum conflito ocorre na interface conforme você vai navegando. Só ressaltando que a barra de endereços não vai mudar, por quê o fiddle carrega o resultado em um iframe então não é possível alterar a URL da janela, mas a página mostra o conteúdo do window.location que é a mesma coisa. Se você ir apertando o “back” do browser (ou o backspace do teclado) você vai ver que a pagina via mudando, fazendo efeito nos links, sem quebrar o efeito e sem refresh.

    http://jsfiddle.net/igorescobar/fJ9wq/

    []’s
    Igor

  15. Olá igor, entendi o conceito,mas estou com dificuldade da implementação no contexto do meu código.

    1 – em todos os exemplos que vi o uso do pushstate/onpopstate, se utilizam de um atributo link (href), enquanto eu estou partindo de um elemento…no meu caso clico em um box, e não em um link . Todas as atribuições no meu código são atribuições de Id, e se encontram em uma div parente ao box clicado. Não compreendo (como disse sou novo em Jquery) como utilizar a variável setCurrentPage nesse contexto.

    2 – No exemplo que você postou, o efeito (fade) não se altera clicando no link, ou com uso do botão back. Mas fiz uma alteração, onde ao clicar no link, o mesmo dá um slideDown em uma outra div com texto simples. A div abre após acesso ao link, mas não se fecha ao clicar em outro link, nem com o botão back. Mas agora Igor, eu sei que o problema é o meu código, ou a minha maneira de pensar o código.

    3 – em seu exemplo, nenhum dos links poder ser acessado diretamente pela barra de endereços.

    Obrigado pelo exemplo, se for possível continuar me ajudando nesse sentido sou grato, mas não quero abusar de sua boa vontade…então vou deixar ao seu critério.
    Se for o caso posto o código novamente no fiddle

    []‘s

  16. @Danilo, Sim! é verdade. A parte da URL responder acessando ela direto, copiando e colando é no servidor. A sua aplicação tem que responder à ela. Então não é para inventar uma URL qualquer, a url vai mudar sem refresh, mas é apenas um útilitario para fazer a navegação fluir melhor. Mas as URLs devem existir, Se você acessar o repositório de qualquer código no Github vai ver 🙂

    A parte de adaptar ao seu caso você terá que se esforçar por aí, tudo bem?

    Espero ter te ajudado.

  17. Igor, minha dificuldade está em abrir páginas e usar url amigaveis.

    Ex.: estou com o menu principal onde tem HOME – QUEM SOMOS – CONTATOS. Esses link funcionam corrente com o pushState e abro as página com Jquery. Porem quando busco uma página e exibo no container, há link dentro dessa página que deverá utilizar a mesma sintaxe do menu principal mas não funciona.

    Deu pra entender?

  18. Rodrigo, da uma lida nos comentários do post pois já foi discutido sobre este assunto em comentários anteriores. É importante que a URL amigável também seja resolvida pela sua aplicação web. Basicamente é assim, não adianta você criar URL via javascript que o seu servidor não conheça, certo? Além dela resolver via javascript ela precisa ser resolvida caso o usuário digite o endereço na mão, sem o auxilio do pushstate.

  19. Opa, cara seu artigo é o mais completo que eu encontrei, parabéns.
    Mas eu achei vários artigos falando de trocar de url sem refresh, eu estou precisando fazer essa alteração de url porem com o refresh, no caso se eu estou na home o usuario clicar no menu para ir em contato, a url ser carregada http://site.com/contato/#content

    Como seria o javascript nesse caso?
    Desde já obrigado!

  20. Ola Igor, tudo bom? Muito bom seu post, parabéns.
    Porém, ao tentar executar seu código de exemplo ocorreu um erro aqui que não consigo identificar: “uncaught typeerror cannot read property ‘url’ of undefined”.

    Teria como me ajudar?
    Desde já obrigado!

  21. Fala Igor, tudo belê? Cara, implementei aqui e está tudo ok, mas, se o usuário pressionar F5 por exemplo, o browser só exibe a página que foi carregada via ajax, o container não. O que fazer?

    Deus abençoe

  22. Olá, Igor!

    Baseada em alguns tutoriais e lendo seu artigo tentei fazer um esquema de
    sites “single page” ou “one page” ou seja todas as páginas estão na home (via include php) e é só rolar… como neste exemplo: http://www.theeddynyc.com

    Só que neste exemplo usaram hash # nas url e eu queria elas “limpas”… pura…

    DÚVIDA:
    No meu “Frankenstein” que foi um código que peguei de outros tutoriais o Google e outros buscadores vão rastrear as urls, todas páginas, ou ele só muda a url no browser, a mágica só acontece localmente?

    Abaixo o meu mostrinho:

    Home – AMMA

    $(document).ready(function() {
    var $ = jQuery;
    $(‘[data-link=”home”]’).click(function() {
    window.history.pushState( location.href, “Home”, “” );
    document.title = “Home”;
    });
    $(‘[data-link=”somos”]’).click(function() {
    window.history.pushState ( location.href, “Quem Somos”, “quem-somos.php” );
    document.title = “Quem Somos”;

    });
    $(‘[data-link=”clinica”]’).click(function() {
    window.history.pushState( location.href, “Clínica”, “clinica.php” );
    document.title = “Clínica”;
    });
    $(‘[data-link=”formacao”]’).click(function() {
    window.history.pushState( location.href, “Formação”, “formacao.php” );
    document.title = “Formação”;

    });
    $(‘[data-link=”agenda”]’).click(function() {
    window.history.pushState( location.href, “Agenda”, “/agenda.php” );
    document.title = “Agenda”;
    });
    $(‘[data-link=”biblioteca”]’).click(function() {
    window.history.pushState( location.href, “Biblioteca”, “biblioteca.php” );
    document.title = “Biblioteca”;
    });
    $(‘[data-link=”fotos”]’).click(function() {
    window.history.pushState( location.href, “Fotos”, “fotos.php” );
    document.title = “Fotos”;
    });
    $(‘[data-link=”contato”]’).click(function() {
    window.history.pushState( location.href, “Contato”, “contato.php” );
    document.title = “Contato”;
    });
    });
    $(function() {
    $(“.menu ul li a”).click(function() {
    $(“html, body”).animate({scrollTop: $(“.pagina-“+$(this).data(‘link’)).offset().top+”px”}, 800);
    return false;
    });
    })

    AMMA

    O Código do menu.php ficou assim:

    HOME
    QUEM SOMOS
    CLÍNICA
    FORMAÇÃO
    AGENDA
    BIBLIOTECA
    FOTOS
    CONTATO

    As páginas de include ficou assim, vou por uma só…rs

    Marcar Consulta
    Atendimento no Consultório Odontológico

    Horários:
    de Segunda à Sexta-feira
    das 08h00 às 18h00
    Aos Sábados das 09h00 às 12h00

    Endereço e Mapa do Consultório

    Atendemos no consultório e home care (escritório, residência e hospital).

    Igor… perdão pelo comentário que mais parece uma consultoria…rs… é que eu venho lutando pra entender o esquema de “history api” do html5 e nada… fico cheia de dúvidas… e aproveitando … qual a difrença de include e include_once?

    Ah, eu trabalhei com vc lá na VISIE…hehe… bons tempos…

    Abraço e te aguardo! Obrigada!

  23. @Glaucia quanto tempo! 🙂
    Sim, a máquina acontece somente local, tudo acontece no browser. Se você quiser que estas paginas sejam indexadas via Google você precisa também referenciar estas páginas utilizando o link com âncora e você também precisa fazer com que o seu “server” renderize esta pagina com esse estilo de URL sem a ajuda do javascript. Basicamente o teste que você precisa fazer é clicar no link, ver a barra de endereço mudar e o conteúdo correto carregado. Depois, copie a URL abra uma nova aba e cole esta URL na barra de endereços. Se abrir a pagina correta, com o conteúdo correto, you’re good to go 😉

  24. Eu usando para fazer um teste!

    window.history.pushState(null, null, ‘http://www.google.com.br/teste’)

    Ele me retorna um erro:

    VM1228:1 Uncaught DOMException: Failed to execute ‘pushState’ on ‘History’: A history state object with URL ‘http://www.google.com.br/teste’ cannot be created in a document with origin ‘http://www.band.uol.com.br’ and URL ‘http://www.band.uol.com.br/playerteste/’.(…)

    Alguem pode saber o que é? como posso arrumar?

Leave a Reply

Your email address will not be published. Required fields are marked *