A API de Histórico HTML5 dá aos programadores a capacidade de modificar a URL de um sítio web sem uma actualização completa da página. Isto é particularmente útil para carregar partes de uma página com JavaScript, de tal forma que o conteúdo é significativamente diferente e garante um novo URL.
Aqui está um exemplo. Digamos que uma pessoa navega desde a página inicial de um sítio até à página de Ajuda. Estamos a carregar o conteúdo dessa página de Ajuda com Ajax. Esse utilizador vai então para a página de Produtos, que carregamos novamente e trocamos o conteúdo com Ajax. Depois, eles querem partilhar o URL. Com a API de Histórico, poderíamos ter alterado o URL da página à medida que o utilizador navega, pelo que o URL que vêem (e assim partilham ou guardam) é relevante e correcto.
The Basics
Para verificar as características desta API é tão simples como entrar nas Ferramentas de Desenvolvimento e digitar history
na consola. Se a API for suportada no seu browser de escolha então encontraremos uma série de métodos anexados a este objecto:
Estamos interessados nos métodos pushState
e replaceState
neste tutorial. Voltando à consola, podemos experimentar um pouco os métodos e ver o que acontece à URL quando os utilizamos. Cobriremos os outros parâmetros nesta função mais tarde, mas por agora só precisamos de utilizar o parâmetro final:
history.replaceState(null, null, 'hello');
The replaceState
method above switches out the URL in the address bar with ‘/hello’ despite no assets being requested and the window remaining on the same page. No entanto, existe aqui um problema. Ao carregarmos no botão voltar, descobriremos que não voltamos ao URL deste artigo, mas em vez disso voltaremos à página em que estávamos antes. Isto é porque replaceState
não manipula o histórico do navegador, simplesmente substitui o URL actual na barra de endereço.
Para corrigir isto, teremos de usar o método pushState
em vez disso:
history.pushState(null, null, 'hello');
Agora, se clicarmos no botão de trás, devemos encontrá-lo a funcionar como gostaríamos, uma vez que pushState
mudou a nossa história para incluir qualquer URL que tenhamos acabado de passar para ele. Isto é interessante, mas o que acontece se tentarmos algo um pouco desonesto e fingirmos que o URL actual não era de todo css-tricks.com, mas sim outro website inteiramente?
history.pushState(null, null, 'https://twitter.com/hello');
Isto irá lançar uma excepção porque o URL tem de ser da mesma origem que o actual, caso contrário podemos arriscar grandes falhas de segurança e dar aos programadores a capacidade de enganar as pessoas a acreditarem que estavam num website completamente diferente.
Retornando aos outros parâmetros que são passados para este método, podemos resumi-los assim:
history.pushState(, , );
- O primeiro parâmetro é os dados que precisaremos se o estado da página web mudar, por exemplo, sempre que alguém carregar no botão para trás ou para a frente no seu navegador. Note que no Firefox estes dados são limitados a 640k caracteres.
-
title
é o segundo parâmetro que pode ser uma string, mas no momento da escrita, cada browser simplesmente ignora-o. - Este parâmetro final é o URL que queremos que apareça na barra de endereço.
Um histórico rápido
A coisa mais significativa com estas API’s de histórico é que elas não recarregam a página. No passado, a única forma de alterar o URL era alterar o window.location
que sempre recarregavam a página. Excepto, se tudo o que mudou foi o hash
(como se clicar num <a href="#target">link</a>
não recarregasse a página).
Isto levou ao velho método hashbang de alterar o URL sem uma actualização completa da página. Famosamente, o Twitter costumava fazer as coisas desta forma e foi largamente criticado por isso (um hash não sendo uma localização de recurso “real”).
Twitter afastou-se disso, e foi um dos primeiros proponentes desta API. Em 2012, a equipa descreveu a sua nova abordagem. Aqui eles esboçam alguns dos seus problemas quando trabalham neste tipo de escala, ao mesmo tempo que detalham como vários navegadores implementam esta especificação.
Um exemplo usando pushState e Ajax
Vamos construir uma demonstração!
Na nossa interface imaginária queremos que os utilizadores do nosso website encontrem informações sobre um personagem de Ghostbusters. Quando seleccionam uma imagem, precisamos que o texto sobre esse personagem apareça por baixo e também queremos adicionar uma classe actual a cada imagem, para que fique claro quem foi seleccionado. Depois, quando clicarmos no botão voltar, a classe actual saltará para o personagem previamente seleccionado (e vice-versa para o botão avançar) e, claro, precisaremos do conteúdo por baixo para voltarmos também a mudar.
Aqui está um exemplo de trabalho que podemos dissecar:
A marcação para este exemplo é suficientemente simples: temos um .gallery
que contém alguns links e dentro de cada um deles está uma imagem. Temos então o texto por baixo do qual queremos actualizar com o nome seleccionado e o vazio .content
div que queremos substituir pelos dados dos respectivos ficheiros HTML de cada caracter:
<div class="gallery"> <a href="/peter.html"> <img src="bill.png" alt="Peter" class="peter" data-name="peter"> </a> <a href="/ray.html"> <img src="ray.png" alt="Ray" class="ray" data-name="ray"> </a> <a href="/egon.html"> <img src="egon.png" alt="Egon" class="egon" data-name="egon"> </a> <a href="/winston.html"> <img src="winston.png" alt="Winston" class="winston" data-name="winston"> </a></div><p class="selected">Ghostbusters</p><p class="highlight"></p><div class="content"></div>
Sem qualquer JavaScript esta página continuará a funcionar como deveria, clicando nos cabeçalhos dos links para a página da direita e clicando no botão de voltar também funciona tal como um utilizador esperaria também. Yay para acessibilidade e degradação graciosa!
P>Próximo salto para JavaScript onde podemos começar a adicionar um manipulador de eventos a cada ligação dentro do elemento .gallery
usando a propagação de eventos, assim:
var container = document.querySelector('.gallery');container.addEventListener('click', function(e) { if (e.target != e.currentTarget) { e.preventDefault(); // e.target is the image inside the link we just clicked. } e.stopPropagation();}, false);
Dentro deste if
declaração podemos então atribuir a data-name
atributo da imagem que seleccionamos à variável data
. Depois anexamos-lhe “.html” e usamos como terceiro parâmetro, o URL que gostaríamos de carregar, no nosso método pushState
(embora, num exemplo real, provavelmente só queríamos alterar o URL depois do pedido Ajax ter sido bem sucedido):
(Alternativamente, também poderíamos agarrar o atributo href do link para isto.)
Substituí o código de trabalho por comentários para que nos possamos concentrar no método pushState
por agora.
Então, neste momento, clicar numa imagem irá actualizar a barra URL e o conteúdo com o pedido Ajax, mas clicar no botão voltar não nos enviará para o carácter anterior que seleccionámos. O que precisamos de fazer aqui é fazer outro pedido Ajax quando o utilizador clicar no botão para trás/para a frente e depois teremos de actualizar a URL mais uma vez com pushState
.
Primeiro vamos voltar atrás e actualizar o parâmetro de estado do nosso método pushState
a fim de guardar essa informação:
history.pushState(data, null, url);
Este é o primeiro parâmetro, data
no método acima. Agora qualquer coisa que esteja definida para essa variável estará acessível para nós num popstate
evento que dispara sempre que o utilizador clica nos botões para a frente ou para trás.
window.addEventListener('popstate', function(e) { // e.state is equal to the data-attribute of the last image we clicked});
Consequentemente podemos então usar esta informação como quisermos, que neste caso está a passar o nome do anterior Ghostbuster que seleccionámos como parâmetro para a função Ajax requestContent
, que usa o método jQuery’s load
:
function requestContent(file) { $('.content').load(file + ' .content');}window.addEventListener('popstate', function(e) { var character = e.state; if (character == null) { removeCurrentClass(); textWrapper.innerHTML = " "; content.innerHTML = " "; document.title = defaultTitle; } else { updateText(character); requestContent(character + ".html"); addCurrentClass(character); document.title = "Ghostbuster | " + character; }});
Se um utilizador clicar na imagem de Ray, o nosso ouvinte de eventos dispararia, o qual guardaria então o atributo de dados da nossa imagem dentro do evento pushState
. Consequentemente, isto carrega o ficheiro ray.html
que será chamado se o utilizador seleccionar outra imagem e depois clicar no botão voltar. *Phew*.
O que é que isto nos deixa? Bem, se clicarmos num caracter e depois partilharmos o URL que actualizámos, então esse ficheiro HTML seria carregado em vez disso. Pode ser uma experiência menos confusa e preservaremos a integridade dos nossos URLs enquanto damos aos nossos utilizadores uma experiência de navegação mais rápida sobre todos.
É importante reconhecer que o exemplo acima é simplista uma vez que carregar conteúdo desta forma com jQuery é muito confuso e provavelmente quereríamos passar um objecto mais complexo para o nosso método pushState
mas mostra-nos como podemos começar imediatamente a aprender como utilizar a API de História. Primeiro caminhamos, depois corremos.
Se fossemos utilizar esta técnica numa escala maior, então provavelmente deveríamos considerar a utilização de uma ferramenta concebida especificamente para esse fim. Por exemplo, o pjax é um plugin jQuery que acelera o processo de utilização simultânea de Ajax e pushState, embora apenas suporte navegadores que utilizam a API de História.
História JS, por outro lado, suporta navegadores mais antigos com o antigo hash-fallback nos URLs.
URLs fixes
Eu gosto de pensar em URLs, e eu referindo-me particularmente a este post no desenho de URLs por Kyle Neath o tempo todo:
URLs são universais. Eles funcionam em Firefox, Chrome, Safari, Internet Explorer, cURL, wget, o seu iPhone, Android e até mesmo escritos em notas pegajosas. Eles são a única sintaxe universal da web. Não tome isso como um dado adquirido. Qualquer utilizador regular semi-técnico do seu site deve ser capaz de navegar 90% da sua aplicação com base na memória da estrutura URL. Para o conseguir, os seus URLs terão de ser pragmáticos.
Isto significa que, independentemente de quaisquer hacks ou truques de aumento de desempenho que possamos querer implementar, os programadores da web devem valorizar o URL e com a ajuda da API do Histórico HTML5 podemos corrigir problemas como o exemplo acima com apenas um pouco de graxa de cotovelo.
Gotchas comuns
- É muitas vezes uma boa ideia incorporar a localização de um pedido Ajax no
href
atributos de um elemento de âncora. - Certifica-te de
return true
dos manipuladores de cliques Javascript quando as pessoas estão no meio ou clicam no comando para que não os sobreponhamos acidentalmente.
Leitura posterior
- Documentação do Mozilla sobre a manipulação do histórico do navegador
- O exemplo da galeria Ajax de Dive into HTML5
- A implementação de pushState
Suporte do navegador
Chrome | Safari | IE | ||||
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |