A restauração de rolagem é um recurso que tendemos a considerar garantido. Com páginas e navegação tradicionais baseadas em HTML (ou seja, sites renderizados por servidor), o navegador sempre lidou com essa interação para nós.
Com a mudança da renderização do lado do servidor para o navegador, os aplicativos renderizados no lado do cliente e até mesmo páginas HTML aprimoradas com JS perderam esse recurso de experiência do usuário muito útil. Um dos lugares mais importantes para ter a restauração de rolagem funcionando corretamente é em uma página de lista de produtos (PLP) em um site de comércio eletrônico. Neste artigo, vamos explorar uma técnica para restaurar manualmente a posição de rolagem do navegador para que nossos usuários de comércio eletrônico tenham uma experiência consistente e de alto desempenho.
Configurando o cenário
Vamos imaginar que temos um site de comércio eletrônico com um PLP que exibe quatro produtos por linha e 32 produtos por página. Cada cartão de produto tem aproximadamente 500 px de altura (o que representa uma imagem do produto, nome do produto, informações de cor/tamanho, preço e um botão”adicionar ao carrinho”).
Agora, vamos supor que um usuário navegue no site tem uma resolução de navegador de 1920 x 1080px. Isso significa que eles podem ver duas linhas de produtos por vez. No total, existem até quatro “janelas de visualização” de rolagem disponíveis para o usuário para este PLP: 32 produtos por página, quatro produtos por linha, oito linhas no total e duas linhas por janela de visualização.
Agora, vamos imaginar que um usuário rolou o PLP para baixo e clicou no 15º produto, de modo que o navegador os navegue para a página de descrição do produto (PDP) desse produto como resultado. Isso significa que ele selecionou um produto na quarta linha.
Agora, o usuário deseja voltar para a página de listagem e continuar a examinar o restante dos produtos, então ele pressiona o botão Voltar. O navegador navega de volta e, quando a página de listagem termina de carregar, os produtos que estão visíveis estão na primeira e na segunda linha.
O usuário então tem que rolar para baixo manualmente, procurando o produto que acabou de obter visto, para continuar a olhar para o resto dos produtos. Essa é uma causa muito comum de frustração com usuários de comércio eletrônico e que faz com que percam a confiança no site e, potencialmente, na marca.
O usuário não tem ideia de por que isso aconteceu, mas como desenvolvedores, nós Faz. Nosso PLP é renderizado e preenchido completamente no navegador, portanto, quando o navegador pinta o HTML (possivelmente incluindo apenas o cabeçalho e o rodapé), a seção onde os produtos são renderizados é apenas um div vazio.
Quando o navegador tenta restaurar nativamente a posição de rolagem usando a coordenada Y onde a barra de rolagem estava localizada antes de navegar para o PDP, a página não será longa o suficiente. O navegador não consegue restaurar a posição de rolagem com precisão, porque não sabe quando nosso aplicativo terminou de renderizar e, portanto, quando deve ser o momento certo para restaurar.
Nessas situações, precisaremos para lidar com a restauração da posição de rolagem nós mesmos.
Antes de começar
Para realmente entender o que é a restauração de rolagem, vamos dar uma olhada em algumas situações em que precisaríamos controlar manualmente a restauração de rolagem: páginas da web renderizadas por servidor com conteúdo renderizado por JS (páginas híbridas) e aplicativos renderizados totalmente no lado do cliente (por exemplo, um aplicativo React).
Primeiro, uma linha de base. Aqui temos um site renderizado por servidor, com todo o HTML disponível para o navegador assim que ele começa a renderizar:
Como podemos ver, navegar de volta para o PLP a partir de um PDP restaura o role perfeitamente até o produto em que clicamos inicialmente antes de navegar para o PDP.
A seguir, temos uma página híbrida. Esta é uma página onde a maior parte do conteúdo HTML está disponível para o navegador na primeira pintura, mas alguns-neste caso, uma lista de produtos-são preenchidos posteriormente por JavaScript.
Podemos ver que há um atraso entre o cabeçalho e o rodapé sendo exibidos e os produtos sendo carregados e renderizados:
Como podemos ver neste exemplo, ao navegar de volta para o PLP, o navegador permanece no topo da janela de visualização e não é capaz de restaurar a rolagem para a posição do produto em que clicamos inicialmente antes de navegar para o PDP.
Finalmente, aqui temos um aplicativo totalmente do lado do cliente desenvolvido com React:
Neste exemplo final, vemos um comportamento semelhante à abordagem híbrida; ou seja, o navegador não é capaz de rolar a restauração para o produto que foi inicialmente clicado.
Implementando a restauração de rolagem
A implementação da restauração de rolagem é muito semelhante para páginas híbridas e totalmente aplicativos do lado do cliente, em que usaremos JavaScript para encontrar o produto para o qual restauraremos nossa localização de rolagem. Para os fins deste artigo, abordaremos implementação da restauração de scroll em um aplicativo React .
Se você gostaria de ver uma implementação de restauração de rolagem para um aplicativo híbrido, verifique o repositório GitHub para este artigo . Faremos referências ao código no repositório às vezes, mas o código importante estará presente neste artigo.
Exemplo de estrutura de aplicativo
Vamos começar com um aplicativo muito básico que possui duas páginas: a primeira página contém 32 produtos com seu código-fonte em um componente chamado PLP.jsx, e a segunda página é simplesmente uma página em branco que atuará como nosso PDP placeholder, com o código-fonte em um componente chamado PDP.jsx.
Também temos um componente ProductCard.jsx, que é usado para renderizar cada um dos 32 produtos.
Etapa 1: Armazenar uma referência do produto selecionado
Primeiro, precisamos armazenar uma referência do produto que o usuário selecionou. Faremos isso usando sessionStorage e um ID do produto.
Por que sessionStorage? Este é apenas o formato escolhido para esta demonstração. Poderíamos facilmente usar um gerente de estado global para armazenar o ID do produto a ser restaurado ou alguma outra forma de manter o valor na memória. Além disso, não precisamos que esses dados permaneçam por muito tempo (como aconteceria com localStorage). Se um usuário fechar a guia, os dados podem ser esquecidos com segurança.
O código necessário para fazer isso acontecer é uma função simples que é chamada quando um link dentro do componente ProductCard é ativado. Para fazer isso, definimos a função em nosso componente PLP e a passamos para o componente ProductCard, chamando-a quando um link é clicado:
PLP.jsx
const PLP=()=> {const persistScrollPosition=(id)=> {sessionStorage.setItem (“scroll-position-product-id-marker”, id); }; return (
ProductCard.jsx
const ProductCard=(props)=> {const {product, onSelect}=props; const {id}=produto; return (
) ; }
Etapa 2: Restaurando manualmente a rolagem para um produto selecionado anteriormente
Agora que armazenamos o produto que foi clicado, precisamos rolar o navegador para esse produto quando o PLP for renderizado novamente.
Para fazer isso, usaremos uma função de retorno de chamada em setState para renderizar o resto do aplicativo e, em seguida, passaremos um restorationRef para o ProductCard que precisa ser rolado para a visualização:
PLP.jsx
const PLP=()=> {//… const [productMarkerId]=React.useState (()=> {//Inicializar lentamente o productMarkerId const persistedId=sessionStorage.getItem (“scroll-position-product-id-marker”); sessionStorage.removeItem (“scroll-position-product-id-marker”); return persistedId? persistedId: null;});//… return (
ProductCard.jsx
const ProductCard=()=> {const {restorationRef}=props; React.useEffect (()=> {//restorationRef é fornecido apenas para o ProductCard que precisa ser rolado para if (! RestorationRef) {return;}//Restaurar a rolagem aqui garante que o produto selecionado anteriormente será sempre restaurado, não importa quanto tempo leva a solicitação da API para obter produtos restorationRef.current.scrollIntoView ({behavior:’auto’, block:’center’});})//…};
A mágica acontece na linha 11 no componente ProductCard.jsx acima. O valor de auto para o comportamento foi selecionado porque a restauração da rolagem do navegador geralmente não usa uma animação, mas se um efeito for desejado, poderíamos facilmente usar smooth.
Quanto ao bloco, o valor do centro tem foi escolhido porque garante que nenhum elemento pegajoso atrapalhe. Por exemplo, se usássemos o valor start e tivéssemos um cabeçalho fixo (algo muito comum no comércio eletrônico), a parte superior da linha do produto seria coberta por esse cabeçalho. Isso não daria uma experiência precisa de restauração de pergaminho, apesar de ser tecnicamente correto.
É isso! Isso é tudo de que precisamos para implementar manualmente a restauração de rolagem.
No entanto, se realmente quisermos ter uma experiência de usuário de qualidade , então devemos dar mais alguns passos.
Passo 3: Restaurando condicionalmente a rolagem para o produto selecionado anteriormente
Imagine este cenário: um cliente seleciona um produto em um PLP e visualiza o página de descrição desse produto. Em seguida, eles usam o menu para navegar para outro PLP e clicam na página de descrição de um produto diferente, antes de usar o menu mais uma vez para voltar ao PLP original. O usuário navegou para frente o tempo todo. Você sabe o que aconteceria?
Bem, a rolagem seria restaurada para o produto que foi selecionado algumas navegações atrás, mesmo que o usuário esteja avançando para a página de listagem. Isso está longe de ser o comportamento ideal e provavelmente causaria confusão para os usuários.
Para contornar isso, podemos adicionar uma verificação à nossa função de inicialização de estado lento que determinará se um usuário está realmente voltando para a página de listagem ou simplesmente navegando até ela novamente de outra página no site.
No React, podemos usar o React Router e o gancho useHistory, que se pareceria com este:
import {useHistory } de’react-router-dom’; const PLP=()=> {const histórico=useHistory ();//… const [productMarkerId]=React.useState (()=> {//A ação do histórico será POP quando um usuário estiver”voltando”para uma página. A alternativa será”PUSH”if (history.action!==’POP’) {return null;}//…}); };
A próxima pergunta é: o que acontece se a página de listagem usa o muito comum padrão de carregamento infinito para fornecer uma lista infinita de produtos para o usuário rola a página para baixo? Quando um usuário clica em um produto e, em seguida, pressiona o botão Voltar, como o aplicativo sabe o que carregar para que o produto certo possa ser restaurado por rolagem? Vamos dar uma olhada nisso a seguir.
Etapa 4: Restauração da rolagem para páginas de listagem com carregamento infinito
A primeira coisa que precisamos garantir para nossa página de listagem de carregamento infinito é que nós’re controlando a página que está sendo carregada. Podemos fazer isso facilmente em um valor de estado, mas isso não seria muito útil para um usuário, especificamente um usuário que deseja compartilhar uma página de produtos.
Devemos considerar o rastreamento da última página carregada de produtos no URL. Isso dá ao usuário um ótimo feedback para ver quantas páginas de produtos eles carregaram (sem ter que pesquisar na IU do aplicativo para tentar encontrar essas informações) e também nos dá a oportunidade de usar o número da página na barra de URL para nossa restauração de rolagem!
Antes de entrarmos no lado técnico das coisas, vamos atualizar nosso cenário de exemplo:
Imagine que nosso site de comércio eletrônico tenha uma única página de listagem. Temos nossos 32 produtos, mas não queremos mostrá-los todos de uma vez e renderizar uma página enorme e longa. Em vez disso, queremos carregar e renderizar apenas os primeiros 12, depois os próximos 12 e depois os oito finais. Faremos isso usando o carregamento infinito.
Conforme o usuário se aproxima da última linha de produtos, ele verá um botão “Carregar próximo”, que carregará a próxima página de produtos, e acrescente-os ao final da lista atual. Conforme as páginas são carregadas, uma string de consulta no URL atualizará? Page=2 para? Page=3 quando a página final for carregada.
Um usuário então seleciona um dos produtos e navega para a página de descrição para esse produto. Quando o usuário terminar de visualizar a página de descrição e acionar o botão Voltar, qual página o navegador carregará?
Quando voltarmos, o navegador irá restabelecer o URL mais recente em que estávamos antes de o evento de navegação, que seria? page=3. Desde que o produto que o usuário selecionou inicialmente estivesse na página três, nossa restauração de rolagem funcionará sem problemas.
No entanto, o que aconteceria se o usuário rolasse para baixo para carregar todas as três páginas de produtos e, em seguida, rolasse novamente para cima e clicou em um produto que está no conjunto de dados da página dois? O que acontece quando eles acionam a ação de retorno na página de descrição?
Bem, a terceira página de resultados ainda será carregada e o produto que o usuário selecionou não será encontrado, então a restauração da rolagem venceu’t ocorrer. O usuário ficará na parte superior da janela de visualização, visualizando a primeira linha de produtos da página três. Isso não é o que o usuário espera, e podemos tornar essa experiência muito melhor.
Lembre-se da função persistScrollPosition que adicionamos ao nosso componente PLP. Podemos adicionar mais uma etapa a isso, que atualizará a string de consulta na URL antes que ocorra o evento de navegação. Lembre-se de que a proposta onSelect de ProductCard é adicionada à proposta onClick do componente Link em ProductCard.
Observe também que, no componente Link, a função onClick será executada antes da navegação para o caminho em para.
Usando esse conhecimento, podemos alterar rapidamente a string de consulta no URL antes que o evento de navegação seja disparado. Isso significa que quando o usuário clica no botão Voltar de uma página de descrição, o navegador carrega a página de produtos que contém o produto que o usuário selecionou inicialmente! Vamos ver isso no código:
const PLP=()=> {//… const persistScrollPosition=(id, pageNo)=> {//Defina o valor da página na string de consulta para corresponder à página que o o produto selecionado está em history.replace (`? page=$ {pageNo}`);//…};//…}
Para manter este artigo focado na técnica de restauração de rolagem, não vou postar todas as alterações necessárias para implementar o carregamento infinito. Se uma referência for necessária, o padrão pode ser encontrado implementado no repositório GitHub previamente vinculado.
Como podemos ver no código acima, simplesmente chamamos history.replace com o número da página de em que o produto selecionado foi carregado. Isso exigirá uma pequena atualização dos dados do produto que são adicionados à matriz de produtos, para que ele possa lembrar a qual página o produto realmente pertence.
Com essa atualização muito simples, podemos confiar no navegador para restabelecer o URL com o número de página correto do produto que o usuário selecionou, independentemente de quantas páginas foram carregadas infinitamente, e nosso mecanismo de restauração de rolagem entrará em ação e funcionará conforme o esperado.
Indo ainda mais longe
Há muito mais que pode ser implementado para a restauração de rolagem para criar uma ótima experiência do usuário. Para não tornar este artigo muito longo, aqui está uma breve visão geral dos recursos extras que podem tornar a experiência do usuário de um site de comércio eletrônico um nível acima do resto.
Skeleton loader
Em vez de exibir uma mensagem básica de carregamento, preencha a página com produtos “placeholder” que, quando substituídos, não causarão problemas de rolagem. O deslocamento da rolagem ocorre quando o navegador tenta rolar a restauração, mas apenas rola para a parte inferior da página (muito curta) porque os produtos ainda não foram renderizados.
Utilizando a propriedade scrollRestoration
Diga ao navegador que vamos lidar com a restauração de rolagem definindo a propriedade scrollRestoration na API History para manual. Isso também é útil para evitar travamento da rolagem.
Acessibilidade
Considere a acessibilidade da restauração da rolagem. Quando um usuário navega de volta, qual elemento receberá o foco? Qual elemento deve receber foco? O produto que o usuário selecionou será restaurado de volta para a janela de visualização, mas o que acontecerá se o usuário pressionar a tecla tab?
Nos exemplos acima, estamos rastreando apenas um único produto para restauração de rolagem, assumindo um padrão PLP simples de PLP → PDP → (voltar). Mas e se o usuário avançar por vários PLPs e PDPs diferentes (usando o menu) e decidir apertar o botão Voltar várias vezes?
O código de exemplo neste artigo não lidará com isso, mas iria ser bastante trivial para atualizá-lo para essa funcionalidade.
Considerações finais
Como mencionado anteriormente, a restauração de rolagem era um recurso que antes dependíamos inteiramente de navegadores para cuidar por nós, então nunca deu um segundo pensamento. No entanto, com muito mais aplicativos da Web progressivos e do lado do cliente sendo desenvolvidos, esse recurso simples está sendo perdido e está tendo um efeito negativo na experiência do usuário de aplicativos de comércio eletrônico.
Neste artigo, nós discutimos algumas etapas simples que podemos seguir para garantir que estejamos criando sites de comércio eletrônico fáceis de navegar e em que os usuários possam confiar.