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, compatível com navegação por teclado e funcione em vários navegadores.

Demonstração

Se você preferir uma demonstração prática de como criar esse componente de Stories, confira o codelab do componente de Stories.

Se preferir vídeo, confira uma versão desta postagem no YouTube:

Visão geral

Dois exemplos conhecidos da UX das Histórias são as Histórias do Snapchat e do Instagram (sem falar dos fleets). Em termos gerais de UX, as Histórias costumam ser um padrão exclusivo para dispositivos móveis e centrado em toques para navegar por várias assinaturas. Por exemplo, no Instagram, os usuários abrem o story de um amigo e passam pelas fotos nele. Geralmente, eles fazem isso com vários amigos de uma vez. Ao tocar no lado direito do dispositivo, o usuário pula para a próxima história desse amigo. Ao deslizar para a direita, o usuário pula para outro amigo. Um componente de matéria é bastante semelhante a um carrossel, mas permite navegar em uma matriz multidimensional em vez de uma 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. Dentro de cada card, há um ou vários cards com bordas ciano. Lista em uma lista.
Primeiro carrossel de amigos
Segundo carrossel "empilhado" de histórias
👍 Lista em uma lista, também conhecida como matriz multidimensional

Escolher as ferramentas certas para o trabalho

No geral, achei esse componente bem simples de criar, graças a alguns recursos críticos da plataforma da Web. Vamos falar sobre elas!

Grade CSS

Nosso layout não foi difícil para o CSS Grid, já que ele tem algumas maneiras eficientes de organizar o conteúdo.

Layout de amigos

Nosso 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 modo dispositivo do Chrome DevTools para destacar as colunas criadas pela grade

Vamos detalhar esse layout grid:

  • Preenchemos explicitamente a janela de visualização 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 ajuste 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, cada coluna será um Story de um amigo. Queremos que as histórias dos amigos continuem fora da janela de visualização para que tenhamos onde rolar. O Grid vai criar quantas colunas forem necessárias para organizar seu HTML em cada história de amigo, criando um contêiner de rolagem dinâmico e responsivo para nós. O Grid nos permitiu centralizar todo o efeito.

Empilhamento

Para cada amigo, precisamos das histórias em um estado pronto para paginação. Para me preparar para animações e outros padrões divertidos, escolhi uma pilha. Quando digo "pilha", quero dizer como se você estivesse 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 em fluxo. Não foi necessário fazer nada com o posicionamento absolute ou z-index. Também não foi necessário corrigir a caixa com height: 100% ou width: 100%. A grade principal já definiu o tamanho da janela de visualização da imagem da matéria. Portanto, nenhum desses componentes precisou ser informado para preenchê-la.

Pontos de ajuste de rolagem CSS

A especificação de pontos de ajuste de rolagem CSS facilita o bloqueio de elementos na janela de visualização ao rolar. Antes da existência dessas propriedades de CSS, era necessário usar JavaScript, o que era… complicado, para dizer o mínimo. Confira Introducing CSS Scroll Snap Points de Sarah Drasner para uma ótima explicação de como usá-los.

Rolagem horizontal sem e com estilos scroll-snap-points. Sem ele, os usuários podem rolar livremente como de costume. Com ele, o navegador fica 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 excessiva 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 ativar a opção de serem um alvo de encaixe.

Escolhi pontos de ajuste de rolagem por alguns motivos:

  • Acessibilidade sem custo financeiro. A especificação de pontos de ajuste de rolagem afirma que pressionar as teclas Seta para a esquerda e Seta para a direita deve mover pelos pontos de ajuste por padrão.
  • Uma especificação em crescimento. A especificação de pontos de ajuste de rolagem está recebendo novos recursos e melhorias o tempo todo, o que significa que meu componente de Stories provavelmente só vai melhorar daqui para frente.
  • Facilidade de implementação. Os pontos de ajuste de rolagem são praticamente criados para o caso de uso de paginação horizontal centrada no toque.
  • Inércia livre no estilo de plataforma. Cada plataforma vai rolar e ficar parada no próprio estilo, em vez de uma inércia normalizada, que pode ter um estilo estranho de rolagem e parada.

Compatibilidade entre 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 nas funcionalidades e no suporte.

No entanto, alguns CSS não foram aplicados, 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 CSS. Ao contrário dos Stories, um carrossel nem sempre precisa parar em cada imagem depois que um usuário interage com ela. Pode ser bom ou até recomendado passar rapidamente pelo carrossel. Já as histórias são melhores para navegar uma por uma, e é exatamente isso que o scroll-snap-stop oferece.

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

No momento em que este post foi escrito, scroll-snap-stop só era compatível com navegadores baseados no Chromium. Confira as atualizações em Compatibilidade de navegadores. No entanto, isso não é um bloqueador. Isso significa apenas que, em navegadores não compatíveis, os usuários podem pular um amigo por acidente. Então, os usuários só precisam ter mais cuidado, ou vamos precisar escrever 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á rolou uma caixa de diálogo modal e, de repente, começou a rolar o conteúdo atrás dela? overscroll-behavior permite que o desenvolvedor prenda essa rolagem e nunca a deixe sair. É bom para todos os tipos de ocasiões. O componente Minhas histórias usa isso para impedir que outros gestos de deslizar e rolar saiam do componente.

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

Safari e Opera foram os dois navegadores que não ofereceram suporte a isso, e não há problema nenhum. Esses usuários terão uma experiência de rolagem em excesso como de costume e talvez nunca percebam essa melhoria. Eu sou muito fã e gosto de incluir esse efeito em quase todos os recursos de rolagem excessiva que implemento. É uma adição inofensiva que só pode melhorar a experiência do usuário.

scrollIntoView({behavior: 'smooth'})

Quando um usuário toca ou clica e chega ao final de um conjunto de stories 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 pedir que ele seja rolado para a visualização. O suporte para os conceitos básicos é ótimo. Todos os navegadores rolaram para mostrar. Mas nem todos os navegadores fizeram isso 'smooth'. Isso significa apenas que ele é rolado para a visualização em vez de ser ajustado.

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

O Safari era o único navegador que não oferecia suporte a behavior: 'smooth' aqui. Confira as atualizações em Compatibilidade de navegadores.

Na prática

Agora que você sabe como eu fiz isso, como você 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 adicionar à seção Remixes da comunidade abaixo.

Remixes da comunidade