Melhorando o fluxo do usuário por meio de transições de página

Publicados: 2022-03-10
Resumo rápido ↬ Sempre que a experiência de um usuário é interrompida, a chance de ele sair aumenta. A mudança de uma página para outra geralmente causa essa interrupção mostrando um flash branco sem conteúdo, demorando muito para carregar ou tirando o usuário do contexto em que estava antes da nova página ser aberta.

As transições entre as páginas podem melhorar a experiência ao reter (ou até mesmo melhorar) o contexto do usuário, manter sua atenção e fornecer continuidade visual e feedback positivo. Ao mesmo tempo, as transições de página também podem ser esteticamente agradáveis ​​e divertidas e podem reforçar a marca quando bem feitas.

Page Transitions

Neste artigo, vamos criar, passo a passo, uma transição entre as páginas. Também falaremos sobre os prós e contras dessa técnica e como levá-la ao limite.

Exemplos

Muitos aplicativos móveis fazem bom uso das transições entre visualizações. No exemplo abaixo, que segue as diretrizes de material design do Google, vemos como a animação transmite relações hierárquicas e espaciais entre as páginas.

Por que não usamos a mesma abordagem com nossos sites? Por que estamos bem com o usuário sentindo que está sendo teletransportado toda vez que a página muda?

Mais depois do salto! Continue lendo abaixo ↓

Como fazer a transição entre páginas da Web

Estruturas de SPA

Antes de sujar as mãos, devo dizer algo sobre estruturas de aplicativos de página única (SPA). Se você estiver usando uma estrutura SPA (como AngularJS, Backbone.js ou Ember), a criação de transições entre páginas será muito mais fácil porque todo o roteamento já é tratado por JavaScript. Consulte a documentação relevante para ver como fazer a transição de páginas usando sua estrutura de escolha, porque provavelmente existem alguns bons exemplos e tutoriais.

O caminho errado

Minha primeira tentativa de criar uma transição entre as páginas ficou mais ou menos assim:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

O conceito é simples: use uma animação quando o usuário sair da página e outra animação quando a nova página for carregada.

No entanto, logo descobri que esta solução tinha algumas limitações:

  • Não sabemos quanto tempo a próxima página levará para carregar, então a animação pode não parecer fluida.
  • Não podemos criar transições que combinem conteúdo das páginas anteriores e seguintes.

Na verdade, a única maneira de obter uma transição fluida e suave é ter controle total sobre o processo de mudança de página e, portanto, não alterar a página . Assim, temos que mudar nossa abordagem ao problema.

O caminho certo

Vejamos as etapas envolvidas na criação de uma transição de crossfade simples entre as páginas da maneira correta. Envolve algo chamado pushState AJAX (ou PJAX), que essencialmente transformará nosso site em uma espécie de site de página única.

Essa técnica não apenas alcança transições suaves e agradáveis, mas também nos beneficiamos de outras vantagens, que abordaremos em detalhes mais adiante neste artigo.

Impedir o comportamento de link padrão

A primeira etapa é criar um ouvinte de evento de click para todos os links a serem usados, evitando que o navegador execute seu comportamento padrão e personalizando a maneira como ele lida com as alterações de página.

 // Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });

Esse método de adicionar um ouvinte de eventos a um elemento pai, em vez de adicioná-lo a cada nó específico, é chamado de delegação de eventos e é possível devido à natureza de borbulhamento de eventos da API HTML DOM.

Buscar a página

Agora que interrompemos o navegador quando ele tenta alterar a página, podemos buscar manualmente essa página usando a API Fetch. Vejamos a função a seguir, que busca o conteúdo HTML de uma página quando recebe seu URL.

 function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }

Para navegadores que não suportam a API Fetch, considere adicionar o polyfill ou usar o bom e velho XMLHttpRequest .

Alterar o URL atual

HTML5 tem uma API fantástica chamada pushState , que permite que sites acessem e modifiquem o histórico do navegador sem carregar nenhuma página. Abaixo, estamos usando-o para modificar o URL atual para ser o URL da próxima página. Observe que esta é uma modificação de nosso manipulador de evento de clique de âncora declarado anteriormente.

 if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }

Como você deve ter notado, também adicionamos uma chamada a uma função chamada changePage , que veremos em detalhes em breve. A mesma função também será chamada no evento popstate , que é acionado quando a entrada do histórico ativo do navegador é alterada (como quando um usuário clica no botão Voltar do navegador):

 window.addEventListener('popstate', changePage);

Com tudo isso, estamos basicamente construindo um sistema de roteamento muito primitivo, no qual temos modos ativo e passivo.

Nosso modo ativo está em uso quando um usuário clica em um link e alteramos a URL usando pushState , enquanto o modo passivo está em uso quando a URL muda e somos notificados pelo evento popstate . Em ambos os casos, vamos chamar changePage , que se encarrega de ler a nova URL e carregar a página relevante.

Analisar e adicionar o novo conteúdo

Normalmente, as páginas navegadas terão elementos comuns, como header e footer . Suponha que usemos a seguinte estrutura DOM em todas as nossas páginas (que na verdade é a estrutura da própria Smashing Magazine):

Animar!

Quando o usuário clica em um link, a função changePage busca o HTML dessa página, extrai o contêiner cc e o adiciona ao elemento main . Neste ponto, temos dois contêineres cc em nossa página, o primeiro pertencente à página anterior e o segundo da página seguinte.

A próxima função, animate , cuida do crossfading dos dois contêineres sobrepondo-os, desvanecendo o antigo, desvanecendo o novo e removendo o contêiner antigo. Neste exemplo, estou usando a API Web Animations para criar a animação de fade, mas é claro que você pode usar qualquer técnica ou biblioteca que desejar.

 function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }

O código final está disponível no GitHub.

E esses são os fundamentos da transição de páginas da web!

Advertências e limitações

O pequeno exemplo que acabamos de criar está longe de ser perfeito. Na verdade, ainda não levamos em consideração algumas coisas:

  • Certifique-se de afetar os links corretos.
    Antes de alterar o comportamento de um link, devemos adicionar uma verificação para garantir que ele seja alterado. Por exemplo, devemos ignorar todos os links com target="_blank" (que abre a página em uma nova guia), todos os links para domínios externos e alguns outros casos especiais, como Control/Command + click (que também abre a página em uma nova aba).
  • Atualize elementos fora do contêiner de conteúdo principal.
    Atualmente, quando a página muda, todos os elementos fora do contêiner cc permanecem os mesmos. No entanto, alguns desses elementos precisariam ser alterados (o que agora só podia ser feito manualmente), incluindo o title do documento, o elemento de menu com a classe active e potencialmente muitos outros dependendo do site.
  • Gerencie o ciclo de vida do JavaScript.
    Nossa página agora se comporta como um SPA, em que o próprio navegador não altera as páginas. Portanto, precisamos cuidar manualmente do ciclo de vida do JavaScript — por exemplo, vincular e desvincular determinados eventos, reavaliar plug-ins e incluir polyfills e código de terceiros.

Suporte ao navegador

O único requisito para este modo de navegação que estamos implementando é a API pushState , que está disponível em todos os navegadores modernos. Esta técnica funciona totalmente como um aprimoramento progressivo . As páginas ainda são servidas e acessíveis da maneira usual, e o site continuará funcionando normalmente quando o JavaScript estiver desabilitado.

Se você estiver usando uma estrutura SPA, considere usar a navegação PJAX, apenas para manter a navegação rápida. Ao fazer isso, você ganha suporte legado e cria um site mais amigável para SEO.

Indo ainda mais longe

Podemos continuar a forçar o limite dessa técnica otimizando certos aspectos dela. Os próximos truques vão acelerar a navegação, melhorando significativamente a experiência do usuário.

Usando um cache

Alterando ligeiramente nossa função loadPage , podemos adicionar um cache simples, que garante que as páginas que já foram visitadas não sejam recarregadas.

 var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }

Como você deve ter adivinhado, podemos usar um cache mais permanente com a API Cache ou outro cache de armazenamento persistente do lado do cliente (como IndexedDB).

Animando a página atual

Nosso efeito crossfade requer que a próxima página esteja carregada e pronta antes que a transição seja concluída. Com outro efeito, podemos querer começar a animar a página antiga assim que o usuário clicar no link, o que daria ao usuário um feedback imediato, uma grande ajuda para o desempenho percebido.

Usando promessas, lidar com esse tipo de situação se torna muito fácil. O método .all cria uma nova promessa que é resolvida assim que todas as promessas incluídas como argumentos são resolvidas.

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

Pré-buscando a próxima página

Usando apenas a navegação PJAX, as alterações de página geralmente são quase duas vezes mais rápidas que a navegação padrão, porque o navegador não precisa analisar e avaliar nenhum script ou estilo na nova página.

No entanto, podemos ir ainda mais longe começando a pré-carregar a próxima página quando o usuário passa o mouse ou começa a tocar no link.

Como você pode ver, geralmente há 200 a 300 milissegundos de atraso no pairar e clicar do usuário. Este é o tempo morto e geralmente é suficiente para carregar a próxima página.

Dito isto, faça a pré-busca com sabedoria, pois pode facilmente se tornar um gargalo. Por exemplo, se você tiver uma longa lista de links e o usuário estiver rolando por ela, essa técnica fará a pré-busca de todas as páginas porque os links estão passando sob o mouse.

Outro fator que podemos detectar e levar em consideração ao decidir se deve ser feita a pré-busca é a velocidade de conexão do usuário. (Talvez isso seja possível no futuro com a API de informações de rede.)

Saída Parcial

Em nossa função loadPage , estamos buscando todo o documento HTML, mas na verdade precisamos apenas do contêiner cc . Se estivermos usando uma linguagem do lado do servidor, podemos detectar se a solicitação vem de uma chamada AJAX personalizada específica e, em caso afirmativo, gerar apenas o contêiner necessário. Ao usar a API de cabeçalhos, podemos enviar um cabeçalho HTTP personalizado em nossa solicitação de busca.

 function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }

Então, no lado do servidor (usando PHP neste caso), podemos detectar se nosso cabeçalho personalizado existe antes de gerar apenas o contêiner necessário:

 if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }

Isso reduzirá o tamanho da mensagem HTTP e também reduzirá a carga do lado do servidor.

Empacotando

Depois de implementar essa técnica em alguns projetos, percebi que uma biblioteca reutilizável seria imensamente útil. Isso me pouparia tempo ao implementá-lo em cada ocasião, liberando-me para me concentrar nos próprios efeitos da transição.

Assim nasceu o Barba.js, uma pequena biblioteca (4 KB minificada e gZip'd) que abstrai toda essa complexidade e fornece uma API agradável, limpa e simples para os desenvolvedores usarem. Ele também é responsável por visualizações e vem com transições reutilizáveis, armazenamento em cache, pré-busca e eventos. É de código aberto e está disponível no GitHub.

Conclusão

Vimos agora como criar um efeito crossfade e os prós e contras de usar a navegação PJAX para transformar efetivamente nosso site em um SPA. Além do benefício da transição em si, também vimos como implementar mecanismos simples de cache e pré-busca para acelerar o carregamento de novas páginas.

Este artigo inteiro é baseado em minha experiência pessoal e no que aprendi com a implementação de transições de página em projetos em que trabalhei. Se você tiver alguma dúvida, não hesite em deixar um comentário ou entrar em contato comigo no Twitter - minhas informações estão abaixo!

Leitura adicional no SmashingMag:

  • Transições inteligentes no design da experiência do usuário
  • Projetando na transição para um mundo de vários dispositivos
  • Fornecendo uma experiência nativa com tecnologias da Web