Como criar um componente de histórias

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

Neste post, 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 você preferir uma demonstração prática de como criar esse componente do Stories, confira o codelab do componente do Stories.

Se preferir vídeos, confira a versão desta postagem no YouTube:

Visão geral

Dois exemplos famosos de UX de Stories são o Snapchat e o Instagram Stories, sem falar das frotas. Em termos gerais de UX, as Histórias geralmente são um padrão focado em toques e exclusivo para dispositivos móveis para navegar em várias assinaturas. Por exemplo, no Instagram, os usuários abrem a história de um amigo e conferem as fotos. Eles geralmente fazem isso com vários amigos ao mesmo tempo. Ao tocar no lado direito do dispositivo, o usuário pula para a próxima história do amigo. Ao deslizar para a direita, o usuário pula para um amigo diferente. Um componente Story é bastante semelhante a um carrossel, mas permite navegar em uma matriz multidimensional em vez de em uma matriz unidimensional. É como se houvesse um carrossel dentro de cada carrossel. 🤯

Matriz multidimensional visualizada usando cards. Da esquerda para a direita, há uma pilha de cards com bordas roxas, e dentro de cada um há vários cards com bordas ciano. Lista em uma lista.
Primeiro carrossel de amigos
Segundo carrossel "empilhado" de histórias
👍 Lista em uma lista, ou seja: uma matriz multidimensional

Como escolher as ferramentas certas para o trabalho

No geral, achei esse componente muito simples de criar, graças a alguns recursos importantes da plataforma da Web. Vamos explicar cada uma delas.

Grade CSS

Nosso layout não exigiu muito do CSS Grid, já que ele tem algumas maneiras eficientes de organizar o conteúdo.

Layout de amigos

O wrapper de componente .stories principal é 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;
}
Usar o Device Mode do Chrome DevTools para destacar as colunas criadas pela grade

Vamos detalhar esse layout de grid:

  • Preenchemos explicitamente a viewport em dispositivos móveis com 100vh e 100vw e restringimos o tamanho em computadores
  • / 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 do Snapchat e do Instagram, cada coluna será a história de um amigo. Queremos que as histórias de amigos continuem fora da viewport para que possamos rolar para baixo. A grade vai criar quantas colunas forem necessárias para o layout do HTML de cada história de amigo, criando um contêiner de rolagem dinâmico e responsivo para nós. A grade nos permitiu centralizar todo o efeito.

Empilhamento

Precisamos das histórias de cada amigo em um estado pronto para paginação. Para me preparar para a animação e outros desenhos divertidos, escolhi uma pilha. Quando digo "empilhamento", quero dizer que você está olhando para baixo em um sanduíche, não como se estivesse olhando de lado.

Com a grade CSS, podemos definir uma grade de célula única (ou seja, um quadrado), em que as linhas e colunas compartilham um alias ([story]), e 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. Não foi necessário fazer nada com o posicionamento absolute ou z-index e não foi necessário corrigir a caixa com height: 100% ou width: 100%. A grade mãe já definiu o tamanho da viewport da imagem da história. Portanto, nenhum desses componentes precisa ser preenchido.

Pontos de quebra de rolagem do CSS

A especificação de pontos de fixação 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 isso era no mínimo complicado. Confira Introdução aos pontos de ajuste de rolagem do CSS de Sarah Drasner para uma explicação detalhada de como usá-los.

Rolagem horizontal sem e com estilos scroll-snap-points. Sem ela, os usuários podem rolar livremente como de costume. Com ele, o navegador descansa suavemente em cada item.
primária
.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;
}
O elemento pai com rolagem 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 podem optar por serem um alvo do Snap.

Escolhi pontos de ajuste de rolagem por alguns motivos:

  • Acessibilidade sem custo financeiro. A especificação dos pontos de ajuste de rolagem indica que, ao pressionar as teclas de seta para a esquerda e seta para a direita, você passa pelos pontos de ajuste por padrão.
  • Uma especificação em crescimento. A especificação de pontos de ajuste de rolagem recebe novos recursos e melhorias o tempo todo, o que significa que meu componente de histórias provavelmente vai melhorar cada vez mais.
  • Facilidade de implementação. Os pontos de ajuste de rolagem são criados praticamente para o caso de uso de paginação horizontal com foco em toque.
  • Inércia livre no estilo de plataforma. Cada plataforma vai rolar e descansar no estilo dela, em vez de inercia normalizada, que pode ter um estilo de rolagem e descanso estranho.

Compatibilidade com vários navegadores

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

No entanto, alguns CSS não se aplicam, então algumas plataformas estão perdendo otimizações de UX. Gostei de não precisar gerenciar esses recursos e tenho certeza de que eles vão chegar a outros navegadores e plataformas.

scroll-snap-stop

Os carrosséis foram um dos principais casos de uso de UX que motivaram a 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 ela. Pode ser aceitável ou encorajado percorrer rapidamente o carrossel. Já as histórias são melhor navegadas uma por vez, e é exatamente isso que o scroll-snap-stop oferece.

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

No momento em que este artigo foi escrito, o scroll-snap-stop só era compatível com navegadores baseados no Chromium. Confira as atualizações em Compatibilidade com navegadores. No entanto, não é um bloqueador. Isso significa apenas que, em navegadores não compatíveis, os usuários podem pular um amigo por acidente. Portanto, os usuários terão apenas que ser mais cuidadosos ou precisaremos programar 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 uma modal quando, de repente, começou a rolar o conteúdo por trás dela? overscroll-behavior permite que o desenvolvedor prenda essa rolagem e nunca deixe que ela saia. É legal para todos os tipos de ocasião. O componente "My Stories" o usa para evitar que outros gestos de deslizar e de rolagem saiam do componente.

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

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

scrollIntoView({behavior: 'smooth'})

Quando um usuário toca ou clica e chega ao final 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, foi possível fazer referência ao próximo amigo e solicitar que ele fosse exibido. O suporte para o básico é ótimo, porque todos os navegadores rolaram a tela para visualização. Mas nem todos os navegadores fizeram isso 'smooth'. Isso significa apenas que ele foi rolado para aparecer em vez de ser fixado.

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

O Safari foi o único navegador que não oferece suporte a behavior: 'smooth'. Confira as atualizações em Compatibilidade com navegadores.

Prático

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

Remixes da comunidade