Front End

29 out, 2018

PushState em 10 minutos

Publicidade

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

um
dois
três
quatro

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.

Links