Como criar um componente de histórias

Uma visão geral básica de como criar uma experiência semelhante aos Stories do Instagram na Web.

Nesta postagem, quero compartilhar ideias sobre como criar um componente de Stories para a Web que seja responsivo, ofereça suporte à navegação por teclado e funcione em todos os navegadores.

Demonstração

Se preferir uma demonstração prática de como criar esse componente Stories, confira o codelab sobre o componente Stories (link em inglês).

Se preferir vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Dois exemplos conhecidos de UX dos Stories são os Stories do Snapchat e os Stories do Instagram (sem contar as frotas). Em termos gerais de UX, as Histórias geralmente são um padrão apenas para dispositivos móveis centrado em toque para navegar por várias assinaturas. Por exemplo, no Instagram, os usuários abrem o story de um amigo e conferem as fotos que aparecem nele. Eles geralmente fazem isso com muitos amigos por vez. Ao tocar no lado direito do dispositivo, o usuário avança para a próxima história desse amigo. Ao deslizar para a direita, o usuário avança para outro amigo. Um componente de história é semelhante a um carrossel, mas permite navegar por uma matriz multidimensional em vez de uma matriz unidimensional. É como se houvesse um carrossel dentro de cada carrossel. 🤯

Visualização de uma matriz multidimensional usando cards. Da esquerda para a direita, há uma pilha de cartões com bordas roxas. Dentro de cada cartão há um ou muitos cartões com borda ciano. em uma lista.
1o carrossel de amigos
2o carrossel "empilhado" de histórias
👍 Lista em uma lista, também conhecida como: uma matriz multidimensional

Como escolher as ferramentas certas para o trabalho

No geral, achei esse componente bastante simples de criar, graças a alguns recursos essenciais da plataforma da Web. Vamos falar sobre eles.

Grade CSS

No fim, nosso layout não era uma tarefa complexa para a CSS Grid, já que ele possui algumas maneiras poderosas de organizar o conteúdo.

Layout de amigos

Nosso wrapper principal do componente .stories é uma visualização de rolagem horizontal que prioriza dispositivos móveis:

.stories {
  inline-size: 100vw;
  block-size: 100vh;

  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}

/* desktop constraint */
@media (hover: hover) and (min-width: 480px) {
  max-inline-size: 480px;
  max-block-size: 848px;
}
Como usar o Device Mode do Chrome DevTools para destacar as colunas criadas pela grade

Vamos detalhar esse layout de grid:

  • Preenchemos explicitamente a janela de visualização em dispositivos móveis com 100vh e 100vw e restringimos o tamanho no computador.
  • / separa nossos modelos de linha e coluna.
  • auto-flow é traduzido para grid-auto-flow: column
  • O modelo de fluxo automático é 100%, que, neste caso, é a largura da janela de rolagem.

Em um smartphone, pense nisso como o tamanho da linha sendo a altura da janela de visualização e cada coluna sendo a largura da janela de visualização. Continuando com o exemplo dos Stories do Snapchat e do Instagram Stories, cada coluna será a história de um amigo. Queremos que as histórias de amigos continuem fora da janela de visualização, para que tenhamos algum lugar para rolar. A grade fará quantas colunas forem necessárias para definir o layout do HTML para cada história de amigo, criando um contêiner de rolagem dinâmico e responsivo para nós. A grade nos possibilitou centralizar todo o efeito.

Acúmulo

Para cada amigo, precisamos de suas histórias em um estado pronto para paginação. Em preparação para animação e outros padrões divertidos, escolhi uma pilha. Quando falo pilha, quero dizer que você está olhando para um sanduíche, não como se estivesse olhando de lado.

Com a grade CSS, é possível definir uma grade de célula única (ou seja, um quadrado), em que as linhas e colunas compartilham um alias ([story]) e, em seguida, cada filho é atribuído a esse espaço de célula única com alias:

.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
.story {
  grid-area: story;
  background-size: cover;
  …
}

Isso coloca nosso HTML no controle da ordem de empilhamento e também mantém todos os elementos no fluxo. Observe como não precisamos fazer nada com o posicionamento absolute ou z-index e não precisamos fazer a caixa correta com height: 100% ou width: 100%. A grade mãe já definiu o tamanho da janela de visualização da imagem da história, então nenhum desses componentes da história precisava ser contado para preenchê-la.

Pontos de alinhamento de rolagem do CSS

A especificação de pontos de ajuste de rolagem do CSS facilita o bloqueio de elementos na janela de visualização durante a rolagem. Antes de existirem essas propriedades CSS, você precisava usar JavaScript, e era... complicado. Confira Introdução aos pontos de resposta de rolagem do CSS (em inglês), de Sarah Drasner, para saber mais sobre como usá-los.

Rolagem horizontal com e sem estilos scroll-snap-points. Sem ele, os usuários podem rolar a tela livremente normalmente. Com ele, o navegador apoia gentilmente cada item.
parent
.stories {
  display: grid;
  grid: 1fr / auto-flow 100%;
  gap: 1ch;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  touch-action: pan-x;
}
Pai com rolagem esticada define o comportamento de ajuste
criança
.user {
  display: grid;
  grid: [story] 1fr / [story] 1fr;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}
As crianças escolhem ser um alvo de ajuste.

Escolhi o recurso de pontos de alinhamento de rolagem por alguns motivos:

  • Acessibilidade sem custo financeiro. A especificação de pontos de ajuste de rolagem declara que pressionar as teclas de seta para a esquerda e para a direita se movem pelos pontos de ajuste por padrão.
  • Uma especificação cada vez maior. A especificação de pontos de ajuste de rolagem está sempre recebendo novos recursos e melhorias. Isso significa que o componente Stories provavelmente vai ficar ainda melhor de agora em diante.
  • Facilidade de implementação. Os pontos de ajuste de rolagem são criados para o caso de uso de paginação horizontal centrado no toque.
  • Inércia sem custo financeiro em estilo de plataforma. Cada plataforma rola e permanece no próprio estilo, ao contrário da inércia normalizada, que pode ter um estilo estranho de rolagem e repouso.

Compatibilidade entre navegadores

Testamos no Opera, Firefox, Safari e Chrome, além de Android e iOS. Veja um breve resumo dos recursos da Web em que encontramos diferenças de recursos e suporte.

No entanto, tínhamos alguns CSS que não se aplicavam, então algumas plataformas estão perdendo otimizações de UX. Gostei de não precisar gerenciar esses recursos e me senti confiante de que eles algum dia chegarão a outros navegadores e plataformas.

scroll-snap-stop

Os carrosséis foram um dos principais casos de uso de UX que levaram à criação da especificação de pontos de ajuste de rolagem do CSS. Ao contrário das histórias, um carrossel nem sempre precisa parar em cada imagem depois que um usuário interage com ele. Pode ser bom ou incentivado a percorrer rapidamente o carrossel. A navegação das Histórias, por outro lado, é melhor uma a uma, e é exatamente isso que scroll-snap-stop oferece.

.user {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

No momento da publicação desta postagem, scroll-snap-stop só é compatível com navegadores baseados no Chromium. Confira as atualizações em Compatibilidade do navegador. No entanto, isso não é um obstáculo. Isso significa apenas que, em navegadores não compatíveis, os usuários podem pular um amigo por acidente. Os usuários só terão que ser mais cuidadosos, ou escreveremos em JavaScript para garantir que um amigo ignorado não seja marcado como visualizado.

Leia mais na especificação se tiver interesse.

overscroll-behavior

Você já estava rolando por um modal e, de repente, começou a rolar o conteúdo por trás dele? O overscroll-behavior permite que o desenvolvedor trave a rolagem e nunca a deixe sair. É bom para todos os tipos de ocasiões. O componente My Stories o usa para evitar que outros gestos de deslizar e rolagem saiam do componente.

.stories {
  overflow-x: auto;
  overscroll-behavior: contain;
}

Safari e Opera eram os dois navegadores que não oferecevam suporte a esse recurso, o que não tem problema. Esses usuários vão ter uma experiência de rolagem como estão acostumados e talvez nunca percebam essa melhoria. Pessoalmente, sou um grande fã e gosto de incluí-lo em quase todos os recursos de rolagem que implemento. É uma adição inofensiva que só pode levar a uma melhor UX.

scrollIntoView({behavior: 'smooth'})

Quando um usuário toca ou clica e chega ao fim do conjunto de histórias de um amigo, é hora de passar para o próximo amigo no conjunto de pontos de ajuste de rolagem. Com JavaScript, conseguimos referenciar o próximo amigo e solicitar que ele seja rolado para visualização. O suporte aos princípios básicos é ótimo: todos os navegadores o rolam para exibição. No entanto, nem todos os navegadores fizeram isso 'smooth'. Isso significa apenas que ele é rolado para visualização, em vez de ajustado.

element.scrollIntoView({
  behavior: 'smooth'
})

O Safari era o único navegador que não tinha suporte ao behavior: 'smooth' aqui. Confira as atualizações em Compatibilidade do navegador.

Prático

Agora que você sabe como eu fiz isso, como você iria?! Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Glitch, envie um tweet para você e vou adicionar sua versão à seção Remixes da comunidade abaixo.

Remixes da comunidade