Gostei muito da ditática passada no último artigo, então resolvi adotar ela para esta possível “série” de artigos.
Hoje falaremos sobre a manipulação de históricos com JavaScript utilizando o método pushState. É importante lembrar que dei uma estudada antes de escrever este artigo e todos os links que estudei estão no final dele, em um compilado do “melhor” de cada link.
Sem mais enrolação, vamos para o artigo!
Muitas vezes precisamos fazer algo que envolve JavaScript em nossa página, e se o usuário navegar para trás no histórico pode afetar completamente sua experiência. Para evitar isso, o DOM window fornece acesso ao histórico do navegador através do objeto history.
Neste artigo tentarei abordar algumas situações (reais e recentes) na qual foi necessária a utilização do método pushState, explicando seu funcionamento.
Básico do objeto history
O Objeto history possui “apenas” alguns métodos, e são eles:
- history.back();
- history.forward();
- history.go();
- history.pushState();
- history.replaceState();
O nome de cada um é descritivo, mas vou explicar para quê cada um serve e vou deixar alguns exemplos.
history.back();
Este método é basicamente o mesmo acionado quando o usuário aperta o botão de voltar do navegador.
history.forward();
A mesma coisa do método acima, porém, avança no histórico do usuário.
history.go();
Este aqui já é um método dinâmico. Ele aceita um parâmetro em número
, e vai para um posicionamento específico do histórico do usuário.
Exemplo:
history.go(2); //avança 2 itens no histórico do usuário
history.go(-3); //volta 3 itens no histórico do usuário
Dica: você pode capturar o tamanho do history utilizando history.length
.
Adicionando e modificando entradas do histórico
O HTML5 introduziu os métodos history.pushState()
e history.replaceState()
que nos permitem modificar e adicionar as entradas do objeto history
.
history.pushState();
Não vou entrar em termos muito técnicos ao explicar. Basicamente, o método adiciona um item ao histórico e “pula” pra ele.
Existem três parâmetros que são passados neste método: data
, title
e url
.
- 1.
[data]
O primeiro parâmetro é a data que precisaremos caso o state
da página mude — quando os métodos back();
e forward();
rodarem no navegador.
De acordo com o CSS-Tricks, essa data tem um limite de 640mil caracteres.
- 2.
[title]
Ele deveria ser o título que seria armazenado no histórico, mas os navegadores ignoram este parâmetro.
- 3.
[url]
Responsável por modificar a url na barra de navegação do seu navegador. Ele recebe uma string e suporta absolutamente qualquer URL. Posteriormente veremos a melhor forma de passar essa informação para o navegador.
Suposição de exemplo retirada da MDN:
Supondo que a URL “http://mozilla.org/foo.html” esteja executando o seguinte JavaScript:
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
Isso fará com que a barra de navegação do seu navegador exiba “http://mozilla.org/bar.html“, mas não vai fazer com que o navegador carregue essa página ou ao menos cheque se ela existe ou não.
history.replaceState()
O método replaceState()
opera quase da mesma forma que o pushState()
. A diferença é que ele altera a entrada atual, ao invés de criar uma nova.
Suposição de exemplo:
Supondo que a url http://foo.com/bar.html rode o seguinte JavaScript:
var stateObj = { bar: "bar2" };
history.pushState(stateObj, "page 2", "bar2.html");
Isso fará com que, ao usuário acessar http://foo.com/bar.html, o navegador exiba http://foo.com/bar.html na barra de endereços e atualize a estrada no histórico. Mas não vai fazer com que a página bar2.html carregue ou ao menos verifique se ela existe.
O que faremos neste artigo
let boxes = Array.from(document.getElementsByClassName('box')); let url = window.location.href; function selectBox (id) { boxes.forEach(b => { b.classList.toggle('selected', b.id === id); }); } boxes.forEach(b => { let id = b.id; b.addEventListener('click', e => { history.pushState({id}, 'Selected: ${id}', '${url}?selected=${id}') selectBox(id); }); }); window.addEventListener('popstate', e => { if(e.state !== null) selectBox(e.state.id); else selectBox(null) });
Repare no link do site: enquanto clica nas caixas, faça testes com os métodos back();
e forward();
.
Como fazer isso?
Este é meu script base. Ele não tem absolutamente nada relacionado com pushState e popstate.
//declaração das variaveis
let boxes = Array.from(document.getElementsByClassName('box'));
let url = window.location.href;
//método responsável por "selecionar" a caixa (alternar a classe)
function selectBox (id) {
boxes.forEach(b => {
b.classList.toggle('selected', b.id === id);
});
}
//Ao clicar nas caixas selecionar cada uma delas
boxes.forEach(b => {
let id = b.id;
b.addEventListener('click', e => {
selectBox(id);
});
});
Nosso objetivo é rodar um pushState()
ao history
toda vez que a gente clicar em uma caixa.
Vamos então adicionar o método antes do método selectBox();
, deixando nosso código assim:
//Ao clicar nas caixas selecionar cada uma delas
boxes.forEach(b => {
let id = b.id;
b.addEventListener('click', e => {
history.pushState({id}, `Selecionado: ${id}`, `${url}?selected=${id}`)
selectBox(id);
});
});
Agora, toda vez que a gente clicar nas caixas, ele vai adicionar um item no histórico. Só que quando a gente clicar pra voltar, ele vai mudar apenas a URL, e não vai selecionar as caixas novamente.
Pra gente selecionar as caixas, precisamos observar o evento onpopstate
. Esse evento é enviado para o window toda vez que há uma mudança na entrada atual do objeto history
.
Na prática, se a entrada do history
for feita com os eventos pushState()
ou replaceState()
, a propriedade state
do evento contém uma cópia da data enviada nas funções.
Então vamos adicionar um event listener no nosso código:
window.addEventListener('popstate', e => {
selectBox(e.state.id);
});
Agora, sim: toda vez que o evento popstate
rodar, a gente vai selecionar a caixa que está contida no nosso objeto passando data. Só que ainda há um porém: quando tentar voltar para o histórico “original” (onde não há uma [data] ainda), esse código vai gerar um error
, pois não vai haver um state
definido.
Resolveremos o erro do state de uma forma bem simples: verificando se ele existe ou não.
window.addEventListener('popstate', e => {
//caso haja um state definido, seleciona a caixa com o id do state
if(e.state !== null)
selectBox(e.state.id);
//caso não haja um state definido, nao seleciona nenhuma caixa
else
selectBox(null)
});
Dica: Para evitar problemas com arquivos inexistentes, opte por passar as opções como parâmetros na url.
Prontinho! Agora você já sabe como manipular o history
de forma eficiente.